All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels
@ 2015-04-17 14:22 Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 01/34] ui: remove check for failure of qemu_acl_init() Daniel P. Berrange
                   ` (34 more replies)
  0 siblings, 35 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

The following series is a work-in-progress of my effort to bring TLS
encryption support to all the QEMU socket based backends. The current
patches just illustrate changes to the chardev backends, but future
patches will cover NBD client/server and the migration client/server
code. The original discussion was here:

https://lists.gnu.org/archive/html/qemu-devel/2015-02/msg00529.html

The bulk of the interesting work here has focused on pulling our the
TLS code from the VNC server and turning it into a self-contained,
reusable API. This has been accompanied with a general consolidation
of all cryptographic related code. So we have one place in the code
base for dealing with cryptographic hash functions (md5, sha, etc),
ciphers (DES, AES, etc), and protocols (TLS). The benefit of this
is that the rest of the code base doesn't have to be littered with
#ifdef HAVE_GNUTLS conditionals - all the integration code for gnutls
is in one place.

Building on that, the next step has been to define a generic I/O
channels API (inspired by GIOChannel, but improved to better suit
QEMU's requirements). This provides a higher level API for dealing
with POSIX sockets, and running protocols such as TLS, SASL, and
WebSockets over them. This again allowed the VNC code to be further
untangled, so it doesn't have to directly know about TLS or WebSockets
for the most part. This will allow us to add WebSockets support to
the chardev backends too at some point.

The patches series is sequenced into a number of logical groupings,
with a view to allowing the patches to be incrementally merged,
rather than having to take the entire series in one go.

 - QOM - a handful of minor enhancements & fixes to QOM, in
   particular some work to make handling of enum properties
   clearer, and to make instantiation of objects with properties
   simpler.

 - Crypto - introduce crypto/ and include/crypto/ directories
   that contain APIs for hash functions, cipher functions and
   the TLS credential (x509 cert) handling and TLS protocol
   itself. This will be further enhanced down the line as &
   when I get time to integrate support for LUKS.

 - Hash conversion - the quorum blockdrv and VNC server are
   converted to use the new crypto hash functions. This removes
   some of the #ifdef CONFIG_* conditionals from their codepaths

 - Cipher conversion - the qcow(2) blockdrv and VNC server are
   converted to use the new crypto cipher functions. This removes
   their direct dependency on QEMU's in-tree AES and DES impls. If
   QEMU is linked to GNUTLS, this will now transparently use either
   libgcrypt or nettle for the AES & DES algorithms. These impls are
   more actively maintained than QEMU's built-in impls and also
   benefit from FIPS certification in some distros. The built-in
   impls of course remain for those building without GNUTLS.

 - I/O Channels - introduce io/ and include/io/ directories that
   contain APIs for dealing with arbitrary bidirectional I/O channels.
   These can be POSIX sockets, fifos, pipes, or higher level layers
   such as TLS or WebSockets. Having a common API for all these
   concepts greatly facilitates the integration of TLS/WebSockets into
   the various areas of code in QEMU that are traditionally hardcoded
   to directly use the POSIX sockets API.

 - VNC conversion - convert the VNC server to use the new I/O channels
   APIs. This enables nearly all the remaining #ifdef CONFIG_*
   conditionals to be removed from the VNC codebase, greatly
   clarifying its code.

 - Chardev conversion - convert the chardev backend to use the new I/O
   channels APIs instead of GLib's GIOChannel. This also includes
   support for enabling TLS on the TCP chardev backend, nicely
   illustrating how the I/O channels APIs simplify support for such
   protocols.

I've attempted to get fairly complete API documentation coverage for
all the new code files I've created here, with example code usage too.
In addition, the crypto modules gain a number of new test suites to
validate correctness of the implementations, since this is critical
code to get right.

What I see still to be done (high == merge block, medium == nice to
have for merge, low == do it later):

 - The code is not fully ported to the Windows platform yet. In
   particular the QIOChannelSocket and QIOChannelFile classes are
   almost certainly broken & if they compile it is just luck. High
   priority.

 - Need to validate the reference counting / lifecycle of the
   QIOChannel objects in chardev & vnc servers to ensure I've not
   introduced any race conditions in their usage vs client disconnects.
   High priority.

 - Unit test coverage of all the QIOChannel subclasses to validate
   their correct operation. Medium priority

 - A QIOChannelMemory implementation that provides a memory buffer
   backed I/O channel. Mostly this will be used for the unit test
   suite, but might find other uses at some point. Medium priority.

 - APIs for establishing socket connections. Currently the
   QIOChannelSocket class is instantiated from a pre-connected socket
   file descriptor. It is desirable to have a constructor that just
   accepts a hostname/service/family and then performs the name
   resolution & connection code. This will make the API more consistent
   to use. Medium priority.

 - A QIOChannelSASL implementation that provides integration for the
   SASL authentication protocol. This will allow the last custom I/O
   layer to be removed from the VNC server code. Low priority, since
   we don't immediately want/need SASL support in chardev/migraton/nbd
   code.

 - A QIOChannelTelnet implementation that runs the telnet protocol, to
   replace the hacky telnet support that is hardwired into the chardev
   backends. Low priority.

 - The crypto API could usefully gain a cipher backend that uses the
   Linux kernel crypto API as an alternative to nettle or libgcrypt.
   Low priority, nice to have.

While I will probably start work on it, I'm not intending to submit
the update of the NBD/migration code, until this series has been
positively reviewed and looks like it is close to accepted for merge,
as there are already enough patches in this series as it is :-) The
aim though is to convert the NBD code to use QIOChannel instead of
direct sockets usage & add the TLS protocol extension previously
discussed with the NBD spec maintainer. The migration code will
either be adapted to use QIOChannel, or the QEMUFile code will be
adapted to use QIOChannel. Undecided which is the best approach there
at this time. Probably depends whether we can do a QIOChannelRDMA
impl that has performance on parity with what exists today.

For those interesting in testing I have made the series available
on github too

  https://github.com/berrange/qemu/tree/qemu-io-channel-7

The diffstat may look alarming but a good portion is in the test
suite and there's some quite verbose comments inline too which
bulk it up:

Daniel P. Berrange (34):
  ui: remove check for failure of qemu_acl_init()
  qom: document user creatable object types in help text
  qom: create objects in two phases
  qom: add object_new_propv / object_new_proplist constructors
  qom: make enum string tables const-correct
  qom: add a object_property_add_enum helper method
  qom: don't pass string table to object_get_enum method
  crypto: introduce new module for computing hash digests
  crypto: move built-in AES implementation into crypto/
  crypto: move built-in D3DES implementation into crypto/
  crypto: introduce generic cipher API & built-in implementation
  crypto: add a gcrypt cipher implementation
  crypto: add a nettle cipher implementation
  crypto: introduce new module for handling TLS credentials
  crypto: add sanity checking of TLS credentials
  crypto: introduce new module for handling TLS sessions
  block: convert quorum blockdrv to use crypto APIs
  ui: convert VNC websockets to use crypto APIs
  block: convert qcow/qcow2 to use generic cipher API
  ui: convert VNC to use generic cipher API
  io: add abstract QIOChannel classes
  io: add helper module for creating watches on UNIX FDs
  io: add QIOChannelSocket class
  io: add QIOChannelFile class
  io: add QIOTask class for async operations
  io: add QIOChannelTLS class
  io: pull Buffer code out of VNC module
  io: add QIOChannelWebsock class
  ui: convert VNC server to use QEMUIOChannelSocket classes
  ui: convert VNC server to use QIOChannelTLS
  ui: convert VNC server to use QIOChannelWebsock
  char: convert from GIOChannel to QIOChannel
  char: don't assume telnet initialization will not block
  char: introduce support for TLS encrypted TCP chardev backend

 Makefile.objs                         |    1 +
 backends/hostmem.c                    |   22 +-
 block/Makefile.objs                   |    2 +-
 block/qcow.c                          |  100 ++-
 block/qcow2-cluster.c                 |   46 +-
 block/qcow2.c                         |   95 +--
 block/qcow2.h                         |   13 +-
 block/quorum.c                        |   38 +-
 configure                             |  213 ++++---
 crypto/Makefile.objs                  |    7 +
 {util => crypto}/aes.c                |    2 +-
 crypto/cipher-builtin.c               |  391 ++++++++++++
 crypto/cipher-gcrypt.c                |  204 ++++++
 crypto/cipher-nettle.c                |  226 +++++++
 crypto/cipher.c                       |   31 +
 ui/d3des.c => crypto/desrfb.c         |    2 +-
 crypto/hash.c                         |  202 ++++++
 crypto/init.c                         |  160 +++++
 crypto/tlscreds.c                     | 1093 ++++++++++++++++++++++++++++++++
 crypto/tlssession.c                   |  546 ++++++++++++++++
 include/{qemu => crypto}/aes.h        |    0
 include/crypto/cipher.h               |  205 ++++++
 ui/d3des.h => include/crypto/desrfb.h |    0
 include/crypto/hash.h                 |  189 ++++++
 include/crypto/init.h                 |   29 +
 include/crypto/tlscreds.h             |  135 ++++
 include/crypto/tlssession.h           |  313 ++++++++++
 include/hw/qdev-core.h                |    2 +-
 include/io/buffer.h                   |  118 ++++
 include/io/channel-file.h             |   67 ++
 include/io/channel-socket.h           |  168 +++++
 include/io/channel-tls.h              |  142 +++++
 include/io/channel-unix.h             |   50 ++
 include/io/channel-websock.h          |  108 ++++
 include/io/channel.h                  |  388 ++++++++++++
 include/io/task.h                     |  168 +++++
 include/qapi/util.h                   |    2 +-
 include/qapi/visitor-impl.h           |    6 +-
 include/qapi/visitor.h                |    2 +-
 include/qom/object.h                  |   78 ++-
 io/Makefile.objs                      |    8 +
 io/buffer.c                           |   65 ++
 io/channel-file.c                     |  198 ++++++
 io/channel-socket.c                   |  572 +++++++++++++++++
 io/channel-tls.c                      |  393 ++++++++++++
 io/channel-unix.c                     |  100 +++
 io/channel-websock.c                  |  976 +++++++++++++++++++++++++++++
 io/channel.c                          |  178 ++++++
 io/task.c                             |   84 +++
 numa.c                                |    1 -
 qapi-schema.json                      |    2 +
 qapi/qapi-dealloc-visitor.c           |    3 +-
 qapi/qapi-util.c                      |    2 +-
 qapi/qapi-visit-core.c                |    6 +-
 qemu-char.c                           |  798 ++++++++++++------------
 qemu-options.hx                       |  137 +++-
 qom/object.c                          |  141 ++++-
 scripts/qapi-types.py                 |    4 +-
 target-arm/crypto_helper.c            |    2 +-
 target-i386/fpu_helper.c              |    1 -
 target-i386/ops_sse.h                 |    2 +-
 target-ppc/int_helper.c               |    2 +-
 tests/.gitignore                      |    9 +
 tests/Makefile                        |   16 +-
 tests/crypto-tls-helpers.c            |  485 +++++++++++++++
 tests/crypto-tls-helpers.h            |  133 ++++
 tests/pkix_asn1_tab.c                 | 1103 +++++++++++++++++++++++++++++++++
 tests/test-crypto-cipher.c            |  290 +++++++++
 tests/test-crypto-hash.c              |  209 +++++++
 tests/test-crypto-tlscreds.c          |  727 ++++++++++++++++++++++
 tests/test-crypto-tlssession.c        |  540 ++++++++++++++++
 ui/Makefile.objs                      |    6 +-
 ui/vnc-auth-sasl.c                    |   81 +--
 ui/vnc-auth-vencrypt.c                |   90 ++-
 ui/vnc-enc-tight.c                    |   38 +-
 ui/vnc-enc-zlib.c                     |    6 +-
 ui/vnc-enc-zrle.c                     |   18 +-
 ui/vnc-jobs.c                         |   25 +-
 ui/vnc-tls.c                          |   24 +-
 ui/vnc-tls.h                          |   69 ---
 ui/vnc-ws.c                           |  393 ++----------
 ui/vnc-ws.h                           |   75 +--
 ui/vnc.c                              |  997 ++++++++++++++---------------
 ui/vnc.h                              |  104 ++--
 util/Makefile.objs                    |    2 +-
 vl.c                                  |   37 +-
 86 files changed, 12868 insertions(+), 1848 deletions(-)
 create mode 100644 crypto/Makefile.objs
 rename {util => crypto}/aes.c (99%)
 create mode 100644 crypto/cipher-builtin.c
 create mode 100644 crypto/cipher-gcrypt.c
 create mode 100644 crypto/cipher-nettle.c
 create mode 100644 crypto/cipher.c
 rename ui/d3des.c => crypto/desrfb.c (99%)
 create mode 100644 crypto/hash.c
 create mode 100644 crypto/init.c
 create mode 100644 crypto/tlscreds.c
 create mode 100644 crypto/tlssession.c
 rename include/{qemu => crypto}/aes.h (100%)
 create mode 100644 include/crypto/cipher.h
 rename ui/d3des.h => include/crypto/desrfb.h (100%)
 create mode 100644 include/crypto/hash.h
 create mode 100644 include/crypto/init.h
 create mode 100644 include/crypto/tlscreds.h
 create mode 100644 include/crypto/tlssession.h
 create mode 100644 include/io/buffer.h
 create mode 100644 include/io/channel-file.h
 create mode 100644 include/io/channel-socket.h
 create mode 100644 include/io/channel-tls.h
 create mode 100644 include/io/channel-unix.h
 create mode 100644 include/io/channel-websock.h
 create mode 100644 include/io/channel.h
 create mode 100644 include/io/task.h
 create mode 100644 io/Makefile.objs
 create mode 100644 io/buffer.c
 create mode 100644 io/channel-file.c
 create mode 100644 io/channel-socket.c
 create mode 100644 io/channel-tls.c
 create mode 100644 io/channel-unix.c
 create mode 100644 io/channel-websock.c
 create mode 100644 io/channel.c
 create mode 100644 io/task.c
 create mode 100644 tests/crypto-tls-helpers.c
 create mode 100644 tests/crypto-tls-helpers.h
 create mode 100644 tests/pkix_asn1_tab.c
 create mode 100644 tests/test-crypto-cipher.c
 create mode 100644 tests/test-crypto-hash.c
 create mode 100644 tests/test-crypto-tlscreds.c
 create mode 100644 tests/test-crypto-tlssession.c
 delete mode 100644 ui/vnc-tls.h

-- 
2.1.0

^ permalink raw reply	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 01/34] ui: remove check for failure of qemu_acl_init()
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 15:56   ` Eric Blake
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 02/34] qom: document user creatable object types in help text Daniel P. Berrange
                   ` (33 subsequent siblings)
  34 siblings, 1 reply; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

The qemu_acl_init() function has long since stopped being able
to return NULL, since g_malloc will abort on OOM. As such the
checks for NULL were unreachable code.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 ui/vnc.c | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/ui/vnc.c b/ui/vnc.c
index cffb5b7..6bfe228 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -3602,10 +3602,6 @@ void vnc_display_open(const char *id, Error **errp)
             aclname = g_strdup_printf("vnc.%s.x509dname", vs->id);
         }
         vs->tls.acl = qemu_acl_init(aclname);
-        if (!vs->tls.acl) {
-            fprintf(stderr, "Failed to create x509 dname ACL\n");
-            exit(1);
-        }
         g_free(aclname);
     }
 #endif
@@ -3619,10 +3615,6 @@ void vnc_display_open(const char *id, Error **errp)
             aclname = g_strdup_printf("vnc.%s.username", vs->id);
         }
         vs->sasl.acl = qemu_acl_init(aclname);
-        if (!vs->sasl.acl) {
-            fprintf(stderr, "Failed to create username ACL\n");
-            exit(1);
-        }
         g_free(aclname);
     }
 #endif
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 02/34] qom: document user creatable object types in help text
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 01/34] ui: remove check for failure of qemu_acl_init() Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 03/34] qom: create objects in two phases Daniel P. Berrange
                   ` (32 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

The QEMU help for -object is essentially useless, just giving users
the generic syntax. Move it down into its own section and introduce
a nested table where each user creatable object can be documented.
The existing memory-backend-file, rng-random and rng-egd object
types are documented.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 qemu-options.hx | 70 ++++++++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 54 insertions(+), 16 deletions(-)

diff --git a/qemu-options.hx b/qemu-options.hx
index 319d971..5ef0ae4 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -3421,22 +3421,6 @@ DEF("no-kvm-irqchip", 0, QEMU_OPTION_no_kvm_irqchip, "", QEMU_ARCH_I386)
 HXCOMM Deprecated (ignored)
 DEF("tdf", 0, QEMU_OPTION_tdf,"", QEMU_ARCH_ALL)
 
-DEF("object", HAS_ARG, QEMU_OPTION_object,
-    "-object TYPENAME[,PROP1=VALUE1,...]\n"
-    "                create an new object of type TYPENAME setting properties\n"
-    "                in the order they are specified.  Note that the 'id'\n"
-    "                property must be set.  These objects are placed in the\n"
-    "                '/objects' path.\n",
-    QEMU_ARCH_ALL)
-STEXI
-@item -object @var{typename}[,@var{prop1}=@var{value1},...]
-@findex -object
-Create an new object of type @var{typename} setting properties
-in the order they are specified.  Note that the 'id'
-property must be set.  These objects are placed in the
-'/objects' path.
-ETEXI
-
 DEF("msg", HAS_ARG, QEMU_OPTION_msg,
     "-msg timestamp[=on|off]\n"
     "                change the format of messages\n"
@@ -3462,6 +3446,60 @@ Dump json-encoded vmstate information for current machine type to file
 in @var{file}
 ETEXI
 
+DEFHEADING(Generic object creation)
+
+DEF("object", HAS_ARG, QEMU_OPTION_object,
+    "-object TYPENAME[,PROP1=VALUE1,...]\n"
+    "                create an new object of type TYPENAME setting properties\n"
+    "                in the order they are specified.  Note that the 'id'\n"
+    "                property must be set.  These objects are placed in the\n"
+    "                '/objects' path.\n",
+    QEMU_ARCH_ALL)
+STEXI
+@item -object @var{typename}[,@var{prop1}=@var{value1},...]
+@findex -object
+Create an new object of type @var{typename} setting properties
+in the order they are specified.  Note that the 'id'
+property must be set.  These objects are placed in the
+'/objects' path.
+
+@table @option
+
+@item -object memory-backend-file,id=@var{id},size=@var{size},mem-path=@var{dir},share=@var{on|off}
+
+Creates a memory file backend object, which can be used to back
+the guest RAM with huge pages. The @option{id} parameter is a
+unique ID that will be used to reference this memory region
+when configuring the @option{-numa} argument. The @option{size}
+option provides the size of the memory region, and accepts
+common suffixes, eg @option{500M}. The @option{mem-path} provides
+the path to either a shared memory or huge page filesystem mount.
+The @option{share} boolean option determines whether the memory
+region is marked as private to QEMU, or shared. The latter allows
+a co-operating external process to access the QEMU memory region.
+
+@item -object rng-random,id=@var{id},filename=@var{/dev/random}
+
+Creates a random number generator backend which obtains entropy from
+a device on the host. The @option{id} parameter is a unique ID that
+will be used to reference this entropy backend from the @option{virtio-rng}
+device. The @option{filename} parameter specifies which file to obtain
+entropy from and if omitted defaults to @option{/dev/random}.
+
+@item -object rng-egd,id=@var{id},chardev=@var{chardevid}
+
+Creates a random number generator backend which obtains entropy from
+an external daemon running on the host. The @option{id} parameter is
+a unique ID that will be used to reference this entropy backend from
+the @option{virtio-rng} device. The @option{chardev} parameter is
+the unique ID of a character device backend that provides the connection
+to the RNG daemon.
+
+@end table
+
+ETEXI
+
+
 HXCOMM This is the last statement. Insert new options before this line!
 STEXI
 @end table
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 03/34] qom: create objects in two phases
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 01/34] ui: remove check for failure of qemu_acl_init() Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 02/34] qom: document user creatable object types in help text Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 04/34] qom: add object_new_propv / object_new_proplist constructors Daniel P. Berrange
                   ` (31 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

Some types of object must be created before chardevs, other types of
object must be created after chardevs. As such there is no option but
to create objects in two phases.

This takes the decision to create as many object types as possible
in the first phase, and only delay those which have a dependency on
the chardevs. Hopefully the set which need delaying will remain
small.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 vl.c | 29 ++++++++++++++++++++++++++++-
 1 file changed, 28 insertions(+), 1 deletion(-)

diff --git a/vl.c b/vl.c
index 74c2681..8aac4ee 100644
--- a/vl.c
+++ b/vl.c
@@ -2591,6 +2591,22 @@ static int machine_set_property(const char *name, const char *value,
     return 0;
 }
 
+
+static bool object_create_phase1(const char *type)
+{
+    /* rng-egd requires that -chardev is processed first,
+     * so we must delay it until phase2 */
+    if (g_str_equal(type, "rng-egd")) {
+        return false;
+    }
+    return true;
+}
+
+static bool object_create_phase2(const char *type)
+{
+    return !object_create_phase1(type);
+}
+
 static int object_create(QemuOpts *opts, void *opaque)
 {
     Error *err = NULL;
@@ -2599,6 +2615,7 @@ static int object_create(QemuOpts *opts, void *opaque)
     void *dummy = NULL;
     OptsVisitor *ov;
     QDict *pdict;
+    bool (*type_predicate)(const char *) = opaque;
 
     ov = opts_visitor_new(opts);
     pdict = qemu_opts_to_qdict(opts, NULL);
@@ -2613,6 +2630,9 @@ static int object_create(QemuOpts *opts, void *opaque)
     if (err) {
         goto out;
     }
+    if (!type_predicate(type)) {
+        goto out;
+    }
 
     qdict_del(pdict, "id");
     visit_type_str(opts_get_visitor(ov), &id, "id", &err);
@@ -4008,6 +4028,12 @@ int main(int argc, char **argv, char **envp)
 
     socket_init();
 
+    if (qemu_opts_foreach(qemu_find_opts("object"),
+                          object_create,
+                          object_create_phase1, 0) != 0) {
+        exit(1);
+    }
+
     if (qemu_opts_foreach(qemu_find_opts("chardev"), chardev_init_func, NULL, 1) != 0)
         exit(1);
 #ifdef CONFIG_VIRTFS
@@ -4027,7 +4053,8 @@ int main(int argc, char **argv, char **envp)
     }
 
     if (qemu_opts_foreach(qemu_find_opts("object"),
-                          object_create, NULL, 0) != 0) {
+                          object_create,
+                          object_create_phase2, 0) != 0) {
         exit(1);
     }
 
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 04/34] qom: add object_new_propv / object_new_proplist constructors
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (2 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 03/34] qom: create objects in two phases Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:55   ` Paolo Bonzini
  2015-04-17 16:11   ` Eric Blake
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 05/34] qom: make enum string tables const-correct Daniel P. Berrange
                   ` (30 subsequent siblings)
  34 siblings, 2 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

It is reasonably common to want to create an object, set a
number of properties, register it in the hierarchy and then
mark it as complete (if a user creatable type). This requires
quite alot of error prone, verbose, boilerplate code to achieve.

The object_new_propv / object_new_proplist constructors will
simplify this task by performing all required steps in one go,
accepting the property names/values as variadic args.

Usage would be:

   Error *err = NULL;
   Object *obj;
   obj = object_new_propv(TYPE_MEMORY_BACKEND_FILE,
                          "hostmem0",
                          &err,
                          "share", "yes",
                          "mem-path", "/dev/shm/somefile",
                          "prealloc", "yes",
                          "size": "1048576",
                          NULL);

Note all property values are passed in string form and will
be parsed into their required data types.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 include/qom/object.h | 58 +++++++++++++++++++++++++++++++++++++++++++++++
 qom/object.c         | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 tests/Makefile       |  2 +-
 3 files changed, 123 insertions(+), 1 deletion(-)

diff --git a/include/qom/object.h b/include/qom/object.h
index d2d7748..223b577 100644
--- a/include/qom/object.h
+++ b/include/qom/object.h
@@ -607,6 +607,64 @@ Object *object_new(const char *typename);
 Object *object_new_with_type(Type type);
 
 /**
+ * object_new_propv:
+ * @typename:  The name of the type of the object to instantiate.
+ * @id: The unique ID of the object
+ * @errp: pointer to error object
+ * @...: list of property names and values
+ *
+ * This function with initialize a new object using heap allocated memory.
+ * The returned object has a reference count of 1, and will be freed when
+ * the last reference is dropped.
+ *
+ * The @id parameter will be used to register the object in the /objects
+ * hierarchy.
+ *
+ * The variadic parameters are a list of pairs of (propname, propvalue)
+ * strings. The propname of NULL indicates the end of the property
+ * list. If the object implements the user creatable interface, the
+ * object will be marked complete once all the properties have been
+ * processed.
+ *
+ *   Error *err = NULL;
+ *   Object *obj;
+ *
+ *   obj = object_new_propv(TYPE_MEMORY_BACKEND_FILE,
+ *                          "hostmem0",
+ *                          &err,
+ *                          "share", "yes",
+ *                          "mem-path", "/dev/shm/somefile",
+ *                          "prealloc", "yes",
+ *                          "size": "1048576",
+ *                          NULL);
+ *
+ *   if (!obj) {
+ *     g_printerr("Cannot create memory backend: %s\n",
+ *                error_get_pretty(err));
+ *   }
+ *
+ * Returns: The newly allocated, instantiated & initialized object.
+ */
+Object *object_new_propv(const char *typename,
+                         const char *id,
+                         Error **errp,
+                         ...);
+
+/**
+ * object_new_proplist:
+ * @typename:  The name of the type of the object to instantiate.
+ * @id: The unique ID of the object
+ * @errp: pointer to error object
+ * @vargs: list of property names and values
+ *
+ * See object_new_propv for documentation.
+ */
+Object *object_new_proplist(const char *typename,
+                            const char *id,
+                            Error **errp,
+                            va_list vargs);
+
+/**
  * object_initialize_with_type:
  * @data: A pointer to the memory to be used for the object.
  * @size: The maximum size available at @data for the object.
diff --git a/qom/object.c b/qom/object.c
index b8dff43..29c7aea 100644
--- a/qom/object.c
+++ b/qom/object.c
@@ -11,6 +11,7 @@
  */
 
 #include "qom/object.h"
+#include "qom/object_interfaces.h"
 #include "qemu-common.h"
 #include "qapi/visitor.h"
 #include "qapi-visit.h"
@@ -439,6 +440,69 @@ Object *object_new(const char *typename)
     return object_new_with_type(ti);
 }
 
+Object *object_new_propv(const char *typename,
+                         const char *id,
+                         Error **errp,
+                         ...)
+{
+    va_list vargs;
+    Object *obj;
+
+    va_start(vargs, errp);
+    obj = object_new_proplist(typename, id, errp, vargs);
+    va_end(vargs);
+
+    return obj;
+}
+
+Object *object_new_proplist(const char *typename,
+                            const char *id,
+                            Error **errp,
+                            va_list vargs)
+{
+    Object *obj;
+    const char *propname;
+
+    obj = object_new(typename);
+
+    if (object_class_is_abstract(object_get_class(obj))) {
+        error_setg(errp, "object type '%s' is abstract", typename);
+        goto error;
+    }
+
+    propname = va_arg(vargs, char *);
+    while (propname != NULL) {
+        const char *value = va_arg(vargs, char *);
+
+        object_property_parse(obj, value, propname, errp);
+        if (*errp) {
+            goto error;
+        }
+        propname = va_arg(vargs, char *);
+    }
+
+    object_property_add_child(container_get(object_get_root(), "/objects"),
+                              id, obj, errp);
+    if (*errp) {
+        goto error;
+    }
+
+    if (object_dynamic_cast(obj, TYPE_USER_CREATABLE)) {
+        user_creatable_complete(obj, errp);
+        if (*errp) {
+            object_property_del(container_get(object_get_root(), "/objects"),
+                                id, &error_abort);
+            goto error;
+        }
+    }
+
+    return obj;
+
+ error:
+    object_unref(obj);
+    return NULL;
+}
+
 Object *object_dynamic_cast(Object *obj, const char *typename)
 {
     if (obj && object_class_dynamic_cast(object_get_class(obj), typename)) {
diff --git a/tests/Makefile b/tests/Makefile
index 55aa745..e212ba9 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -240,7 +240,7 @@ test-qapi-obj-y = tests/test-qapi-visit.o tests/test-qapi-types.o \
 
 $(test-obj-y): QEMU_INCLUDES += -Itests
 QEMU_CFLAGS += -I$(SRC_PATH)/tests
-qom-core-obj = qom/object.o qom/qom-qobject.o qom/container.o
+qom-core-obj = qom/object.o qom/qom-qobject.o qom/container.o qom/object_interfaces.o
 
 tests/check-qint$(EXESUF): tests/check-qint.o libqemuutil.a
 tests/check-qstring$(EXESUF): tests/check-qstring.o libqemuutil.a
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 05/34] qom: make enum string tables const-correct
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (3 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 04/34] qom: add object_new_propv / object_new_proplist constructors Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:56   ` Paolo Bonzini
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 06/34] qom: add a object_property_add_enum helper method Daniel P. Berrange
                   ` (29 subsequent siblings)
  34 siblings, 1 reply; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

The enum string table parameters in various QOM/QAPI methods
are declared 'const char *strings[]'. This results in const
warnings if passed a variable that was declared as

   static const char * const strings[] = { .... };

Add the extra const annotation to the parameters, since
neither the string elements, nor the array itself should
ever be modified.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 include/hw/qdev-core.h      | 2 +-
 include/qapi/util.h         | 2 +-
 include/qapi/visitor-impl.h | 6 +++---
 include/qapi/visitor.h      | 2 +-
 include/qom/object.h        | 2 +-
 qapi/qapi-dealloc-visitor.c | 3 ++-
 qapi/qapi-util.c            | 2 +-
 qapi/qapi-visit-core.c      | 6 +++---
 qom/object.c                | 2 +-
 scripts/qapi-types.py       | 4 ++--
 10 files changed, 16 insertions(+), 15 deletions(-)

diff --git a/include/hw/qdev-core.h b/include/hw/qdev-core.h
index 4e673f9..913963e 100644
--- a/include/hw/qdev-core.h
+++ b/include/hw/qdev-core.h
@@ -236,7 +236,7 @@ struct Property {
 struct PropertyInfo {
     const char *name;
     const char *description;
-    const char **enum_table;
+    const char * const *enum_table;
     int (*print)(DeviceState *dev, Property *prop, char *dest, size_t len);
     ObjectPropertyAccessor *get;
     ObjectPropertyAccessor *set;
diff --git a/include/qapi/util.h b/include/qapi/util.h
index de9238b..7ad26c0 100644
--- a/include/qapi/util.h
+++ b/include/qapi/util.h
@@ -11,7 +11,7 @@
 #ifndef QAPI_UTIL_H
 #define QAPI_UTIL_H
 
-int qapi_enum_parse(const char *lookup[], const char *buf,
+int qapi_enum_parse(const char * const lookup[], const char *buf,
                     int max, int def, Error **errp);
 
 #endif
diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
index 09bb0fd..f4a2f74 100644
--- a/include/qapi/visitor-impl.h
+++ b/include/qapi/visitor-impl.h
@@ -30,7 +30,7 @@ struct Visitor
     GenericList *(*next_list)(Visitor *v, GenericList **list, Error **errp);
     void (*end_list)(Visitor *v, Error **errp);
 
-    void (*type_enum)(Visitor *v, int *obj, const char *strings[],
+    void (*type_enum)(Visitor *v, int *obj, const char * const strings[],
                       const char *kind, const char *name, Error **errp);
     void (*get_next_type)(Visitor *v, int *kind, const int *qobjects,
                           const char *name, Error **errp);
@@ -59,9 +59,9 @@ struct Visitor
     void (*end_union)(Visitor *v, bool data_present, Error **errp);
 };
 
-void input_type_enum(Visitor *v, int *obj, const char *strings[],
+void input_type_enum(Visitor *v, int *obj, const char * const strings[],
                      const char *kind, const char *name, Error **errp);
-void output_type_enum(Visitor *v, int *obj, const char *strings[],
+void output_type_enum(Visitor *v, int *obj, const char * const strings[],
                       const char *kind, const char *name, Error **errp);
 
 #endif
diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
index 5934f59..00ba104 100644
--- a/include/qapi/visitor.h
+++ b/include/qapi/visitor.h
@@ -43,7 +43,7 @@ void visit_optional(Visitor *v, bool *present, const char *name,
                     Error **errp);
 void visit_get_next_type(Visitor *v, int *obj, const int *qtypes,
                          const char *name, Error **errp);
-void visit_type_enum(Visitor *v, int *obj, const char *strings[],
+void visit_type_enum(Visitor *v, int *obj, const char * const strings[],
                      const char *kind, const char *name, Error **errp);
 void visit_type_int(Visitor *v, int64_t *obj, const char *name, Error **errp);
 void visit_type_uint8(Visitor *v, uint8_t *obj, const char *name, Error **errp);
diff --git a/include/qom/object.h b/include/qom/object.h
index 223b577..dfdba2f 100644
--- a/include/qom/object.h
+++ b/include/qom/object.h
@@ -1011,7 +1011,7 @@ int64_t object_property_get_int(Object *obj, const char *name,
  * an enum).
  */
 int object_property_get_enum(Object *obj, const char *name,
-                             const char *strings[], Error **errp);
+                             const char * const strings[], Error **errp);
 
 /**
  * object_property_get_uint16List:
diff --git a/qapi/qapi-dealloc-visitor.c b/qapi/qapi-dealloc-visitor.c
index a14a1c7..d7f92c5 100644
--- a/qapi/qapi-dealloc-visitor.c
+++ b/qapi/qapi-dealloc-visitor.c
@@ -156,7 +156,8 @@ static void qapi_dealloc_type_size(Visitor *v, uint64_t *obj, const char *name,
 {
 }
 
-static void qapi_dealloc_type_enum(Visitor *v, int *obj, const char *strings[],
+static void qapi_dealloc_type_enum(Visitor *v, int *obj,
+                                   const char * const strings[],
                                    const char *kind, const char *name,
                                    Error **errp)
 {
diff --git a/qapi/qapi-util.c b/qapi/qapi-util.c
index 1d8fb96..bcdc94d 100644
--- a/qapi/qapi-util.c
+++ b/qapi/qapi-util.c
@@ -14,7 +14,7 @@
 #include "qapi/error.h"
 #include "qapi/util.h"
 
-int qapi_enum_parse(const char *lookup[], const char *buf,
+int qapi_enum_parse(const char * const lookup[], const char *buf,
                     int max, int def, Error **errp)
 {
     int i;
diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
index b66b93a..a18ba16 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -89,7 +89,7 @@ void visit_get_next_type(Visitor *v, int *obj, const int *qtypes,
     }
 }
 
-void visit_type_enum(Visitor *v, int *obj, const char *strings[],
+void visit_type_enum(Visitor *v, int *obj, const char * const strings[],
                      const char *kind, const char *name, Error **errp)
 {
     v->type_enum(v, obj, strings, kind, name, errp);
@@ -260,7 +260,7 @@ void visit_type_number(Visitor *v, double *obj, const char *name, Error **errp)
     v->type_number(v, obj, name, errp);
 }
 
-void output_type_enum(Visitor *v, int *obj, const char *strings[],
+void output_type_enum(Visitor *v, int *obj, const char * const strings[],
                       const char *kind, const char *name,
                       Error **errp)
 {
@@ -279,7 +279,7 @@ void output_type_enum(Visitor *v, int *obj, const char *strings[],
     visit_type_str(v, &enum_str, name, errp);
 }
 
-void input_type_enum(Visitor *v, int *obj, const char *strings[],
+void input_type_enum(Visitor *v, int *obj, const char * const strings[],
                      const char *kind, const char *name,
                      Error **errp)
 {
diff --git a/qom/object.c b/qom/object.c
index 29c7aea..2534398 100644
--- a/qom/object.c
+++ b/qom/object.c
@@ -1025,7 +1025,7 @@ int64_t object_property_get_int(Object *obj, const char *name,
 }
 
 int object_property_get_enum(Object *obj, const char *name,
-                             const char *strings[], Error **errp)
+                             const char * const strings[], Error **errp)
 {
     StringOutputVisitor *sov;
     StringInputVisitor *siv;
diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
index db87218..c8d6db6 100644
--- a/scripts/qapi-types.py
+++ b/scripts/qapi-types.py
@@ -118,7 +118,7 @@ struct %(name)s
 
 def generate_enum_lookup(name, values):
     ret = mcgen('''
-const char *%(name)s_lookup[] = {
+const char * const %(name)s_lookup[] = {
 ''',
                          name=name)
     i = 0
@@ -140,7 +140,7 @@ const char *%(name)s_lookup[] = {
 
 def generate_enum(name, values):
     lookup_decl = mcgen('''
-extern const char *%(name)s_lookup[];
+extern const char * const %(name)s_lookup[];
 ''',
                 name=name)
 
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 06/34] qom: add a object_property_add_enum helper method
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (4 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 05/34] qom: make enum string tables const-correct Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:56   ` Paolo Bonzini
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 07/34] qom: don't pass string table to object_get_enum method Daniel P. Berrange
                   ` (28 subsequent siblings)
  34 siblings, 1 reply; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

A QOM property can be parsed as enum using the visit_type_enum()
helper method, but this forces callers to use the more complex
generic object_property_add() method when registering it. It
also requires that users of that object have access to the
string map when they want to read the property value.

This patch introduces a specialized object_property_add_enum()
method which simplifies the use of enum properties, so the
setters/getters directly get passed the int value.

  typedef enum {
     MYDEV_TYPE_FROG,
     MYDEV_TYPE_ALLIGATOR,
     MYDEV_TYPE_PLATYPUS,

     MYDEV_TYPE_LAST
  } MyDevType;

Then provide a table of enum <-> string mappings

  static const char *const mydevtypemap[MYDEV_TYPE_LAST + 1] = {
     [MYDEV_TYPE_FROG] = "frog",
     [MYDEV_TYPE_ALLIGATOR] = "alligator",
     [MYDEV_TYPE_PLATYPUS] = "platypus",
     [MYDEV_TYPE_LAST] = NULL,
  };

Assuming an object struct of

   typedef struct {
      Object parent;
      MyDevType devtype;
      ...other fields...
   } MyDev;

The property can then be registered as follows:

   static int mydev_prop_get_devtype(Object *obj,
                                     Error **errp G_GNUC_UNUSED)
   {
       MyDev *dev = MYDEV(obj);

       return dev->devtype;
   }

   static void mydev_prop_set_devtype(Object *obj,
                                      int value,
                                      Error **errp G_GNUC_UNUSED)
   {
       MyDev *dev = MYDEV(obj);

       dev->endpoint = value;
   }

   object_property_add_enum(obj, "devtype",
                            mydevtypemap,
                            mydev_prop_get_devtype,
                            mydev_prop_set_devtype,
                            NULL);

Note there is no need to check the range of 'value' in
the setter, because the string->enum conversion code will
have already done that and reported an error as required.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 include/qom/object.h | 17 ++++++++++++++++
 qom/object.c         | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 74 insertions(+)

diff --git a/include/qom/object.h b/include/qom/object.h
index dfdba2f..3462821 100644
--- a/include/qom/object.h
+++ b/include/qom/object.h
@@ -1262,6 +1262,23 @@ void object_property_add_bool(Object *obj, const char *name,
                               Error **errp);
 
 /**
+ * object_property_add_enum:
+ * @obj: the object to add a property to
+ * @name: the name of the property
+ * @get: the getter or NULL if the property is write-only.
+ * @set: the setter or NULL if the property is read-only
+ * @errp: if an error occurs, a pointer to an area to store the error
+ *
+ * Add a enum property using getters/setters.  This function will add a
+ * property of type 'enum'.
+ */
+void object_property_add_enum(Object *obj, const char *name,
+                              const char * const *strings,
+                              int (*get)(Object *, Error **),
+                              void (*set)(Object *, int, Error **),
+                              Error **errp);
+
+/**
  * object_property_add_tm:
  * @obj: the object to add a property to
  * @name: the name of the property
diff --git a/qom/object.c b/qom/object.c
index 2534398..543cc57 100644
--- a/qom/object.c
+++ b/qom/object.c
@@ -1607,6 +1607,63 @@ void object_property_add_bool(Object *obj, const char *name,
     }
 }
 
+typedef struct EnumProperty {
+    const char * const *strings;
+    int (*get)(Object *, Error **);
+    void (*set)(Object *, int, Error **);
+} EnumProperty;
+
+static void property_get_enum(Object *obj, Visitor *v, void *opaque,
+                              const char *name, Error **errp)
+{
+    EnumProperty *prop = opaque;
+    int value;
+
+    value = prop->get(obj, errp);
+    visit_type_enum(v, &value, prop->strings, NULL, name, errp);
+}
+
+static void property_set_enum(Object *obj, Visitor *v, void *opaque,
+                              const char *name, Error **errp)
+{
+    EnumProperty *prop = opaque;
+    int value;
+
+    visit_type_enum(v, &value, prop->strings, NULL, name, errp);
+    prop->set(obj, value, errp);
+}
+
+static void property_release_enum(Object *obj, const char *name,
+                                  void *opaque)
+{
+    EnumProperty *prop = opaque;
+    g_free(prop);
+}
+
+void object_property_add_enum(Object *obj, const char *name,
+                              const char * const *strings,
+                              int (*get)(Object *, Error **),
+                              void (*set)(Object *, int, Error **),
+                              Error **errp)
+{
+    Error *local_err = NULL;
+    EnumProperty *prop = g_malloc0(sizeof(*prop));
+
+    prop->strings = strings;
+    prop->get = get;
+    prop->set = set;
+
+    object_property_add(obj, name, "enum",
+                        get ? property_get_enum : NULL,
+                        set ? property_set_enum : NULL,
+                        property_release_enum,
+                        prop, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        g_free(prop);
+    }
+}
+
 typedef struct TMProperty {
     void (*get)(Object *, struct tm *, Error **);
 } TMProperty;
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 07/34] qom: don't pass string table to object_get_enum method
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (5 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 06/34] qom: add a object_property_add_enum helper method Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 15:05   ` Paolo Bonzini
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 08/34] crypto: introduce new module for computing hash digests Daniel P. Berrange
                   ` (27 subsequent siblings)
  34 siblings, 1 reply; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

Now that properties can be explicitly registered as an enum
type, there is no need to pass the string table to the
object_get_enum method. The object property registration
already has a pointer to the string table.

In changing this method signature, the hostmem backend object
has to be converted to use the new enum property registration
code, which simplifies it somewhat.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 backends/hostmem.c   | 22 ++++++++--------------
 include/qom/object.h |  3 +--
 numa.c               |  1 -
 qom/object.c         | 32 ++++++++++++++++++++++++--------
 4 files changed, 33 insertions(+), 25 deletions(-)

diff --git a/backends/hostmem.c b/backends/hostmem.c
index b7b6cf8..c3b9df1 100644
--- a/backends/hostmem.c
+++ b/backends/hostmem.c
@@ -113,24 +113,17 @@ host_memory_backend_set_host_nodes(Object *obj, Visitor *v, void *opaque,
 #endif
 }
 
-static void
-host_memory_backend_get_policy(Object *obj, Visitor *v, void *opaque,
-                               const char *name, Error **errp)
+static int
+host_memory_backend_get_policy(Object *obj, Error **errp G_GNUC_UNUSED)
 {
     HostMemoryBackend *backend = MEMORY_BACKEND(obj);
-    int policy = backend->policy;
-
-    visit_type_enum(v, &policy, HostMemPolicy_lookup, NULL, name, errp);
+    return backend->policy;
 }
 
 static void
-host_memory_backend_set_policy(Object *obj, Visitor *v, void *opaque,
-                               const char *name, Error **errp)
+host_memory_backend_set_policy(Object *obj, int policy, Error **errp)
 {
     HostMemoryBackend *backend = MEMORY_BACKEND(obj);
-    int policy;
-
-    visit_type_enum(v, &policy, HostMemPolicy_lookup, NULL, name, errp);
     backend->policy = policy;
 
 #ifndef CONFIG_NUMA
@@ -252,9 +245,10 @@ static void host_memory_backend_init(Object *obj)
     object_property_add(obj, "host-nodes", "int",
                         host_memory_backend_get_host_nodes,
                         host_memory_backend_set_host_nodes, NULL, NULL, NULL);
-    object_property_add(obj, "policy", "str",
-                        host_memory_backend_get_policy,
-                        host_memory_backend_set_policy, NULL, NULL, NULL);
+    object_property_add_enum(obj, "policy",
+                             HostMemPolicy_lookup,
+                             host_memory_backend_get_policy,
+                             host_memory_backend_set_policy, NULL);
 }
 
 MemoryRegion *
diff --git a/include/qom/object.h b/include/qom/object.h
index 3462821..bfad22f 100644
--- a/include/qom/object.h
+++ b/include/qom/object.h
@@ -1003,7 +1003,6 @@ int64_t object_property_get_int(Object *obj, const char *name,
  * object_property_get_enum:
  * @obj: the object
  * @name: the name of the property
- * @strings: strings corresponding to enums
  * @errp: returns an error if this function fails
  *
  * Returns: the value of the property, converted to an integer, or
@@ -1011,7 +1010,7 @@ int64_t object_property_get_int(Object *obj, const char *name,
  * an enum).
  */
 int object_property_get_enum(Object *obj, const char *name,
-                             const char * const strings[], Error **errp);
+                             Error **errp);
 
 /**
  * object_property_get_uint16List:
diff --git a/numa.c b/numa.c
index c975fb2..881a123 100644
--- a/numa.c
+++ b/numa.c
@@ -457,7 +457,6 @@ static int query_memdev(Object *obj, void *opaque)
 
         m->value->policy = object_property_get_enum(obj,
                                                     "policy",
-                                                    HostMemPolicy_lookup,
                                                     &err);
         if (err) {
             goto error;
diff --git a/qom/object.c b/qom/object.c
index 543cc57..e99b3c9 100644
--- a/qom/object.c
+++ b/qom/object.c
@@ -1024,13 +1024,35 @@ int64_t object_property_get_int(Object *obj, const char *name,
     return retval;
 }
 
+typedef struct EnumProperty {
+    const char * const *strings;
+    int (*get)(Object *, Error **);
+    void (*set)(Object *, int, Error **);
+} EnumProperty;
+
+
 int object_property_get_enum(Object *obj, const char *name,
-                             const char * const strings[], Error **errp)
+                             Error **errp)
 {
     StringOutputVisitor *sov;
     StringInputVisitor *siv;
     char *str;
     int ret;
+    ObjectProperty *prop = object_property_find(obj, name, errp);
+    EnumProperty *enumprop;
+
+    if (prop == NULL) {
+        return 0;
+    }
+
+    if (!g_str_equal(prop->type, "enum")) {
+        error_setg(errp, "Property %s on %s is not an 'enum' type",
+                   name, object_class_get_name(
+                       object_get_class(obj)));
+        return 0;
+    }
+
+    enumprop = prop->opaque;
 
     sov = string_output_visitor_new(false);
     object_property_get(obj, string_output_get_visitor(sov), name, errp);
@@ -1038,7 +1060,7 @@ int object_property_get_enum(Object *obj, const char *name,
     siv = string_input_visitor_new(str);
     string_output_visitor_cleanup(sov);
     visit_type_enum(string_input_get_visitor(siv),
-                    &ret, strings, NULL, name, errp);
+                    &ret, enumprop->strings, NULL, name, errp);
 
     g_free(str);
     string_input_visitor_cleanup(siv);
@@ -1607,12 +1629,6 @@ void object_property_add_bool(Object *obj, const char *name,
     }
 }
 
-typedef struct EnumProperty {
-    const char * const *strings;
-    int (*get)(Object *, Error **);
-    void (*set)(Object *, int, Error **);
-} EnumProperty;
-
 static void property_get_enum(Object *obj, Visitor *v, void *opaque,
                               const char *name, Error **errp)
 {
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 08/34] crypto: introduce new module for computing hash digests
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (6 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 07/34] qom: don't pass string table to object_get_enum method Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-05-13 17:04   ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 09/34] crypto: move built-in AES implementation into crypto/ Daniel P. Berrange
                   ` (26 subsequent siblings)
  34 siblings, 1 reply; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

Introduce a new crypto/ directory that will (eventually) contain
all the cryptographic related code. This initially defines a
wrapper for initializing gnutls and for computing hashes with
gnutls. The former ensures that gnutls is guaranteed to be
initialized exactly once in QEMU regardless of CLI args. The
block quorum code currently fails to initialize gnutls so it
only works by luck, if VNC server TLS is not requested. The
hash APIs avoids the need to litter the rest of the code with
preprocessor checks and simplifies callers by allocating the
correct amount of memory for the requested hash.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 Makefile.objs            |   1 +
 configure                |  46 +++++++++++
 crypto/Makefile.objs     |   2 +
 crypto/hash.c            | 202 +++++++++++++++++++++++++++++++++++++++++++++
 crypto/init.c            |  62 ++++++++++++++
 include/crypto/hash.h    | 189 ++++++++++++++++++++++++++++++++++++++++++
 include/crypto/init.h    |  29 +++++++
 tests/.gitignore         |   1 +
 tests/Makefile           |   2 +
 tests/test-crypto-hash.c | 209 +++++++++++++++++++++++++++++++++++++++++++++++
 vl.c                     |   8 ++
 11 files changed, 751 insertions(+)
 create mode 100644 crypto/Makefile.objs
 create mode 100644 crypto/hash.c
 create mode 100644 crypto/init.c
 create mode 100644 include/crypto/hash.h
 create mode 100644 include/crypto/init.h
 create mode 100644 tests/test-crypto-hash.c

diff --git a/Makefile.objs b/Makefile.objs
index 28999d3..0f4dd84 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -2,6 +2,7 @@
 # Common libraries for tools and emulators
 stub-obj-y = stubs/
 util-obj-y = util/ qobject/ qapi/ qapi-types.o qapi-visit.o qapi-event.o
+util-obj-y += crypto/
 
 #######################################################################
 # block-obj-y is code used by both qemu system emulation and qemu-img
diff --git a/configure b/configure
index 6969f6f..48b4491 100755
--- a/configure
+++ b/configure
@@ -330,6 +330,8 @@ glusterfs_zerofill="no"
 archipelago="no"
 gtk=""
 gtkabi=""
+gnutls=""
+gnutls_hash=""
 vte=""
 tpm="yes"
 libssh2=""
@@ -1104,6 +1106,10 @@ for opt do
   ;;
   --enable-gtk) gtk="yes"
   ;;
+  --disable-gnutls) gnutls="no"
+  ;;
+  --enable-gnutls) gnutls="yes"
+  ;;
   --enable-rdma) rdma="yes"
   ;;
   --disable-rdma) rdma="no"
@@ -1281,6 +1287,8 @@ Advanced options (experts only):
   --disable-gtk            disable gtk UI
   --enable-gtk             enable gtk UI
   --with-gtkabi            select preferred GTK ABI 2.0 or 3.0
+  --disable-gnutls         disable gnutls crypto features
+  --enable-gnutls          enable gnutls crypto features
   --disable-virtfs         disable VirtFS
   --enable-virtfs          enable VirtFS
   --disable-vnc            disable VNC
@@ -2132,6 +2140,36 @@ if test "$gtk" != "no"; then
     fi
 fi
 
+
+##########################################
+# GNUTLS probe
+
+if test "$gnutls" != "no"; then
+    if $pkg_config --exists "gnutls"; then
+        gnutls_cflags=`$pkg_config --cflags gnutls`
+        gnutls_libs=`$pkg_config --libs gnutls`
+        libs_softmmu="$gnutls_libs $libs_softmmu"
+        libs_tools="$gnutls_libs $libs_tools"
+	QEMU_CFLAGS="$QEMU_CFLAGS $gnutls_cflags"
+        gnutls="yes"
+
+	# gnutls_hash_init requires >= 2.9.10
+	if $pkg_config --exists "gnutls >= 2.9.10"; then
+            gnutls_hash="yes"
+	else
+	    gnutls_hash="no"
+	fi
+    elif test "$gnutls" = "yes"; then
+	feature_not_found "gnutls" "Install gnutls devel"
+    else
+        gnutls="no"
+        gnutls_hash="no"
+    fi
+else
+    gnutls_hash="no"
+fi
+
+
 ##########################################
 # VTE probe
 
@@ -4359,6 +4397,8 @@ fi
 echo "pixman            $pixman"
 echo "SDL support       $sdl"
 echo "GTK support       $gtk"
+echo "GNUTLS support    $gnutls"
+echo "GNUTLS hash       $gnutls_hash"
 echo "VTE support       $vte"
 echo "curses support    $curses"
 echo "curl support      $curl"
@@ -4710,6 +4750,12 @@ if test "$gtk" = "yes" ; then
   echo "CONFIG_GTKABI=$gtkabi" >> $config_host_mak
   echo "GTK_CFLAGS=$gtk_cflags" >> $config_host_mak
 fi
+if test "$gnutls" = "yes" ; then
+  echo "CONFIG_GNUTLS=y" >> $config_host_mak
+fi
+if test "$gnutls_hash" = "yes" ; then
+  echo "CONFIG_GNUTLS_HASH=y" >> $config_host_mak
+fi
 if test "$vte" = "yes" ; then
   echo "CONFIG_VTE=y" >> $config_host_mak
   echo "VTE_CFLAGS=$vte_cflags" >> $config_host_mak
diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs
new file mode 100644
index 0000000..03cc1b2
--- /dev/null
+++ b/crypto/Makefile.objs
@@ -0,0 +1,2 @@
+util-obj-y += init.o
+util-obj-y += hash.o
diff --git a/crypto/hash.c b/crypto/hash.c
new file mode 100644
index 0000000..044a0a5
--- /dev/null
+++ b/crypto/hash.c
@@ -0,0 +1,202 @@
+/*
+ * QEMU Crypto hash algorithms
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/hash.h"
+
+#include <glib/gi18n.h>
+
+#ifdef CONFIG_GNUTLS_HASH
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+static int qcrypto_hash_alg_map[QCRYPTO_HASH_ALG_LAST] = {
+    [QCRYPTO_HASH_ALG_MD5] = GNUTLS_DIG_MD5,
+    [QCRYPTO_HASH_ALG_SHA1] = GNUTLS_DIG_SHA1,
+    [QCRYPTO_HASH_ALG_SHA256] = GNUTLS_DIG_SHA256,
+};
+
+gboolean qcrypto_hash_supports(QCryptoHashAlgorithm alg)
+{
+    if (alg < G_N_ELEMENTS(qcrypto_hash_alg_map)) {
+        return true;
+    }
+    return false;
+}
+
+int qcrypto_hash_bytesv(QCryptoHashAlgorithm alg,
+                        const struct iovec *iov,
+                        size_t niov,
+                        uint8_t **result,
+                        size_t *resultlen,
+                        Error **errp)
+{
+    int i, ret;
+    gnutls_hash_hd_t dig;
+
+    if (alg > G_N_ELEMENTS(qcrypto_hash_alg_map)) {
+        error_setg(errp,
+                   _("Unknown hash algorithm %d"),
+                   alg);
+        return -1;
+    }
+
+    ret = gnutls_hash_init(&dig, qcrypto_hash_alg_map[alg]);
+
+    if (ret < 0) {
+        error_setg(errp,
+                   _("Unable to initialize hash algorithm: %s"),
+                   gnutls_strerror(ret));
+        return -1;
+    }
+
+    for (i = 0; i < niov; i++) {
+        ret = gnutls_hash(dig, iov[i].iov_base, iov[i].iov_len);
+        if (ret < 0) {
+            error_setg(errp,
+                       _("Unable process hash data: %s"),
+                       gnutls_strerror(ret));
+            goto error;
+        }
+    }
+
+    ret = gnutls_hash_get_len(qcrypto_hash_alg_map[alg]);
+    if (ret <= 0) {
+        error_setg(errp,
+                   _("Unable to get hash length: %s"),
+                   gnutls_strerror(ret));
+        goto error;
+    }
+    if (*resultlen == 0) {
+        *resultlen = ret;
+        *result = g_new0(uint8_t, *resultlen);
+    } else if (*resultlen != ret) {
+        error_setg(errp,
+                   _("Result buffer size %zu is smaller than hash %d"),
+                   *resultlen, ret);
+        goto error;
+    }
+
+    gnutls_hash_deinit(dig, *result);
+    return 0;
+
+ error:
+    gnutls_hash_deinit(dig, NULL);
+    return -1;
+}
+
+#else /* ! CONFIG_GNUTLS_HASH */
+
+gboolean qcrypto_hash_supports(QCryptoHashAlgorithm alg G_GNUC_UNUSED)
+{
+    return false;
+}
+
+int qcrypto_hash_bytesv(QCryptoHashAlgorithm alg,
+                        const struct iovec *iov G_GNUC_UNUSED,
+                        size_t niov G_GNUC_UNUSED,
+                        uint8_t **result G_GNUC_UNUSED,
+                        size_t *resultlen G_GNUC_UNUSED,
+                        Error **errp)
+{
+    error_setg(errp,
+               _("Hash algorithm %d not supported without GNUTLS"),
+               alg);
+    return -1;
+}
+
+#endif /* ! CONFIG_GNUTLS_HASH */
+
+int qcrypto_hash_bytes(QCryptoHashAlgorithm alg,
+                       const char *buf,
+                       size_t len,
+                       uint8_t **result,
+                       size_t *resultlen,
+                       Error **errp)
+{
+    struct iovec iov = { .iov_base = (char *)buf,
+                         .iov_len = len };
+    return qcrypto_hash_bytesv(alg, &iov, 1, result, resultlen, errp);
+}
+
+static const char hex[] = "0123456789abcdef";
+
+int qcrypto_hash_digestv(QCryptoHashAlgorithm alg,
+                         const struct iovec *iov,
+                         size_t niov,
+                         char **digest,
+                         Error **errp)
+{
+    uint8_t *result = NULL;
+    size_t resultlen = 0;
+    size_t i;
+
+    if (qcrypto_hash_bytesv(alg, iov, niov, &result, &resultlen, errp) < 0) {
+        return -1;
+    }
+
+    *digest = g_new0(char, (resultlen * 2) + 1);
+    for (i = 0 ; i < resultlen ; i++) {
+        (*digest)[(i * 2)] = hex[(result[i] >> 4) & 0xf];
+        (*digest)[(i * 2) + 1] = hex[result[i] & 0xf];
+    }
+    (*digest)[resultlen * 2] = '\0';
+    g_free(result);
+    return 0;
+}
+
+int qcrypto_hash_digest(QCryptoHashAlgorithm alg,
+                        const char *buf,
+                        size_t len,
+                        char **digest,
+                        Error **errp)
+{
+    struct iovec iov = { .iov_base = (char *)buf, .iov_len = len };
+
+    return qcrypto_hash_digestv(alg, &iov, 1, digest, errp);
+}
+
+int qcrypto_hash_base64v(QCryptoHashAlgorithm alg,
+                         const struct iovec *iov,
+                         size_t niov,
+                         char **base64,
+                         Error **errp)
+{
+    uint8_t *result = NULL;
+    size_t resultlen = 0;
+
+    if (qcrypto_hash_bytesv(alg, iov, niov, &result, &resultlen, errp) < 0) {
+        return -1;
+    }
+
+    *base64 = g_base64_encode(result, resultlen);
+    g_free(result);
+    return 0;
+}
+
+int qcrypto_hash_base64(QCryptoHashAlgorithm alg,
+                        const char *buf,
+                        size_t len,
+                        char **base64,
+                        Error **errp)
+{
+    struct iovec iov = { .iov_base = (char *)buf, .iov_len = len };
+
+    return qcrypto_hash_base64v(alg, &iov, 1, base64, errp);
+}
diff --git a/crypto/init.c b/crypto/init.c
new file mode 100644
index 0000000..8fd66d4
--- /dev/null
+++ b/crypto/init.c
@@ -0,0 +1,62 @@
+/*
+ * QEMU Crypto initialization
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/init.h"
+
+#include <glib/gi18n.h>
+
+#ifdef CONFIG_GNUTLS
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+/* #define DEBUG_GNUTLS */
+
+#ifdef DEBUG_GNUTLS
+static void qcrypto_gnutls_log(int level, const char *str)
+{
+    fprintf(stderr, "%d: %s", level, str);
+}
+#endif
+
+int qcrypto_init(Error **errp)
+{
+    int ret;
+    ret = gnutls_global_init();
+    if (ret < 0) {
+        error_setg(errp,
+                   _("Unable to initialize GNUTLS library: %s"),
+                   gnutls_strerror(ret));
+        return -1;
+    }
+#ifdef DEBUG_GNUTLS
+    gnutls_global_set_log_level(10);
+    gnutls_global_set_log_function(qcrypto_gnutls_log);
+#endif
+    return 0;
+}
+
+#else /* ! CONFIG_GNUTLS */
+
+int qcrypto_init(Error **errp G_GNUC_UNUSED)
+{
+    return 0;
+}
+
+#endif /* ! CONFIG_GNUTLS */
diff --git a/include/crypto/hash.h b/include/crypto/hash.h
new file mode 100644
index 0000000..b5acbf6
--- /dev/null
+++ b/include/crypto/hash.h
@@ -0,0 +1,189 @@
+/*
+ * QEMU Crypto hash algorithms
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QCRYPTO_HASH_H__
+#define QCRYPTO_HASH_H__
+
+#include "qemu-common.h"
+#include "qapi/error.h"
+
+typedef enum {
+    QCRYPTO_HASH_ALG_MD5,
+    QCRYPTO_HASH_ALG_SHA1,
+    QCRYPTO_HASH_ALG_SHA256,
+
+    QCRYPTO_HASH_ALG_LAST
+} QCryptoHashAlgorithm;
+
+
+/**
+ * qcrypto_hash_supports:
+ * @alg: the hash algorithm
+ *
+ * Determine if @alg hash algorithm is supported by the
+ * current configured build.
+ *
+ * Returns: true if the algorithm is supported, false otherwise
+ */
+gboolean qcrypto_hash_supports(QCryptoHashAlgorithm alg);
+
+/**
+ * qcrypto_hash_bytesv:
+ * @alg: the hash algorithm
+ * @iov: the array of memory regions to hash
+ * @niov: the length of @iov
+ * @result: pointer to hold output hash
+ * @resultlen: pointer to hold length of @result
+ * @errp: pointer to uninitialized error object
+ *
+ * Computes the hash across all the memory regions
+ * present in @iov. The @result pointer will be
+ * filled with raw bytes representing the computed
+ * hash, which will have length @resultlen. The
+ * memory pointer in @result must be released
+ * with a call to g_free() when no longer required.
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int qcrypto_hash_bytesv(QCryptoHashAlgorithm alg,
+                        const struct iovec *iov,
+                        size_t niov,
+                        uint8_t **result,
+                        size_t *resultlen,
+                        Error **errp);
+
+/**
+ * qcrypto_hash_bytes:
+ * @alg: the hash algorithm
+ * @buf: the memory region to hash
+ * @len: the length of @buf
+ * @result: pointer to hold output hash
+ * @resultlen: pointer to hold length of @result
+ * @errp: pointer to uninitialized error object
+ *
+ * Computes the hash across all the memory region
+ * @buf of length @len. The @result pointer will be
+ * filled with raw bytes representing the computed
+ * hash, which will have length @resultlen. The
+ * memory pointer in @result must be released
+ * with a call to g_free() when no longer required.
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int qcrypto_hash_bytes(QCryptoHashAlgorithm alg,
+                       const char *buf,
+                       size_t len,
+                       uint8_t **result,
+                       size_t *resultlen,
+                       Error **errp);
+
+/**
+ * qcrypto_hash_digestv:
+ * @alg: the hash algorithm
+ * @iov: the array of memory regions to hash
+ * @niov: the length of @iov
+ * @digest: pointer to hold output hash
+ * @errp: pointer to uninitialized error object
+ *
+ * Computes the hash across all the memory regions
+ * present in @iov. The @digest pointer will be
+ * filled with the printable hex digest of the computed
+ * hash, which will be terminated by '\0'. The
+ * memory pointer in @digest must be released
+ * with a call to g_free() when no longer required.
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int qcrypto_hash_digestv(QCryptoHashAlgorithm alg,
+                         const struct iovec *iov,
+                         size_t niov,
+                         char **digest,
+                         Error **errp);
+
+/**
+ * qcrypto_hash_digest:
+ * @alg: the hash algorithm
+ * @buf: the memory region to hash
+ * @len: the length of @buf
+ * @digest: pointer to hold output hash
+ * @errp: pointer to uninitialized error object
+ *
+ * Computes the hash across all the memory region
+ * @buf of length @len. The @digest pointer will be
+ * filled with the printable hex digest of the computed
+ * hash, which will be terminated by '\0'. The
+ * memory pointer in @digest must be released
+ * with a call to g_free() when no longer required.
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int qcrypto_hash_digest(QCryptoHashAlgorithm alg,
+                        const char *buf,
+                        size_t len,
+                        char **digest,
+                        Error **errp);
+
+/**
+ * qcrypto_hash_base64v:
+ * @alg: the hash algorithm
+ * @iov: the array of memory regions to hash
+ * @niov: the length of @iov
+ * @base64: pointer to hold output hash
+ * @errp: pointer to uninitialized error object
+ *
+ * Computes the hash across all the memory regions
+ * present in @iov. The @base64 pointer will be
+ * filled with the base64 encoding of the computed
+ * hash, which will be terminated by '\0'. The
+ * memory pointer in @base64 must be released
+ * with a call to g_free() when no longer required.
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int qcrypto_hash_base64v(QCryptoHashAlgorithm alg,
+                         const struct iovec *iov,
+                         size_t niov,
+                         char **base64,
+                         Error **errp);
+
+/**
+ * qcrypto_hash_base64:
+ * @alg: the hash algorithm
+ * @buf: the memory region to hash
+ * @len: the length of @buf
+ * @base64: pointer to hold output hash
+ * @errp: pointer to uninitialized error object
+ *
+ * Computes the hash across all the memory region
+ * @buf of length @len. The @base64 pointer will be
+ * filled with the base64 encoding of the computed
+ * hash, which will be terminated by '\0'. The
+ * memory pointer in @base64 must be released
+ * with a call to g_free() when no longer required.
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int qcrypto_hash_base64(QCryptoHashAlgorithm alg,
+                        const char *buf,
+                        size_t len,
+                        char **base64,
+                        Error **errp);
+
+#endif /* QCRYPTO_HASH_H__ */
diff --git a/include/crypto/init.h b/include/crypto/init.h
new file mode 100644
index 0000000..5fc510c
--- /dev/null
+++ b/include/crypto/init.h
@@ -0,0 +1,29 @@
+/*
+ * QEMU Crypto initialization
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QCRYPTO_INIT_H__
+#define QCRYPTO_INIT_H__
+
+#include "qemu-common.h"
+#include "qapi/error.h"
+
+int qcrypto_init(Error **errp);
+
+#endif /* QCRYPTO_INIT_H__ */
diff --git a/tests/.gitignore b/tests/.gitignore
index 0dcb618..12d2373 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -9,6 +9,7 @@ rcutorture
 test-aio
 test-bitops
 test-coroutine
+test-crypto-hash
 test-cutils
 test-hbitmap
 test-int128
diff --git a/tests/Makefile b/tests/Makefile
index e212ba9..ba7d64e 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -72,6 +72,7 @@ check-unit-y += tests/test-qemu-opts$(EXESUF)
 gcov-files-test-qemu-opts-y = qom/test-qemu-opts.c
 check-unit-y += tests/test-write-threshold$(EXESUF)
 gcov-files-test-write-threshold-y = block/write-threshold.c
+check-unit-$(CONFIG_GNUTLS_HASH) += tests/test-crypto-hash$(EXESUF)
 
 check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
 
@@ -309,6 +310,7 @@ tests/test-opts-visitor$(EXESUF): tests/test-opts-visitor.o $(test-qapi-obj-y) l
 
 tests/test-mul64$(EXESUF): tests/test-mul64.o libqemuutil.a
 tests/test-bitops$(EXESUF): tests/test-bitops.o libqemuutil.a
+tests/test-crypto-hash$(EXESUF): tests/test-crypto-hash.o libqemuutil.a libqemustub.a
 
 libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o
 libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o
diff --git a/tests/test-crypto-hash.c b/tests/test-crypto-hash.c
new file mode 100644
index 0000000..911437e
--- /dev/null
+++ b/tests/test-crypto-hash.c
@@ -0,0 +1,209 @@
+/*
+ * QEMU Crypto hash algorithms
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <glib.h>
+
+#include "crypto/init.h"
+#include "crypto/hash.h"
+
+#define INPUT_TEXT "Hiss hisss Hissss hiss Hiss hisss Hiss hiss"
+#define INPUT_TEXT1 "Hiss hisss "
+#define INPUT_TEXT2 "Hissss hiss "
+#define INPUT_TEXT3 "Hiss hisss Hiss hiss"
+
+#define OUTPUT_MD5 "628d206371563035ab8ef62f492bdec9"
+#define OUTPUT_SHA1 "b2e74f26758a3a421e509cee045244b78753cc02"
+#define OUTPUT_SHA256 "bc757abb0436586f392b437e5dd24096" \
+                      "f7f224de6b74d4d86e2abc6121b160d0"
+
+#define OUTPUT_MD5_B64 "Yo0gY3FWMDWrjvYvSSveyQ=="
+#define OUTPUT_SHA1_B64 "sudPJnWKOkIeUJzuBFJEt4dTzAI="
+#define OUTPUT_SHA256_B64 "vHV6uwQ2WG85K0N+XdJAlvfyJN5rdNTYbiq8YSGxYNA="
+
+static const char *expected_outputs[] = {
+    [QCRYPTO_HASH_ALG_MD5] = OUTPUT_MD5,
+    [QCRYPTO_HASH_ALG_SHA1] = OUTPUT_SHA1,
+    [QCRYPTO_HASH_ALG_SHA256] = OUTPUT_SHA256,
+};
+static const char *expected_outputs_b64[] = {
+    [QCRYPTO_HASH_ALG_MD5] = OUTPUT_MD5_B64,
+    [QCRYPTO_HASH_ALG_SHA1] = OUTPUT_SHA1_B64,
+    [QCRYPTO_HASH_ALG_SHA256] = OUTPUT_SHA256_B64,
+};
+static const int expected_lens[] = {
+    [QCRYPTO_HASH_ALG_MD5] = 16,
+    [QCRYPTO_HASH_ALG_SHA1] = 20,
+    [QCRYPTO_HASH_ALG_SHA256] = 32,
+};
+
+static const char hex[] = "0123456789abcdef";
+
+/* Test with dynamic allocation */
+static void test_hash_alloc(void)
+{
+    size_t i;
+
+    g_assert(qcrypto_init(NULL) == 0);
+
+    for (i = 0; i < G_N_ELEMENTS(expected_outputs) ; i++) {
+        uint8_t *result = NULL;
+        size_t resultlen = 0;
+        int ret;
+        size_t j;
+
+        ret = qcrypto_hash_bytes(i,
+                                 INPUT_TEXT,
+                                 strlen(INPUT_TEXT),
+                                 &result,
+                                 &resultlen,
+                                 NULL);
+        g_assert(ret == 0);
+        g_assert(resultlen == expected_lens[i]);
+
+        for (j = 0; j < resultlen; j++) {
+            g_assert(expected_outputs[i][j * 2] == hex[(result[j] >> 4) & 0xf]);
+            g_assert(expected_outputs[i][j * 2 + 1] == hex[result[j] & 0xf]);
+        }
+        g_free(result);
+    }
+}
+
+/* Test with caller preallocating */
+static void test_hash_prealloc(void)
+{
+    size_t i;
+
+    g_assert(qcrypto_init(NULL) == 0);
+
+    for (i = 0; i < G_N_ELEMENTS(expected_outputs) ; i++) {
+        uint8_t *result;
+        size_t resultlen;
+        int ret;
+        size_t j;
+
+        resultlen = expected_lens[i];
+        result = g_new0(uint8_t, resultlen);
+
+        ret = qcrypto_hash_bytes(i,
+                                 INPUT_TEXT,
+                                 strlen(INPUT_TEXT),
+                                 &result,
+                                 &resultlen,
+                                 NULL);
+        g_assert(ret == 0);
+
+        g_assert(resultlen == expected_lens[i]);
+        for (j = 0; j < resultlen; j++) {
+            g_assert(expected_outputs[i][j * 2] == hex[(result[j] >> 4) & 0xf]);
+            g_assert(expected_outputs[i][j * 2 + 1] == hex[result[j] & 0xf]);
+        }
+        g_free(result);
+    }
+}
+
+
+/* Test with dynamic allocation */
+static void test_hash_iov(void)
+{
+    size_t i;
+
+    g_assert(qcrypto_init(NULL) == 0);
+
+    for (i = 0; i < G_N_ELEMENTS(expected_outputs) ; i++) {
+        struct iovec iov[3] = {
+            { .iov_base = (char *)INPUT_TEXT1, .iov_len = strlen(INPUT_TEXT1) },
+            { .iov_base = (char *)INPUT_TEXT2, .iov_len = strlen(INPUT_TEXT2) },
+            { .iov_base = (char *)INPUT_TEXT3, .iov_len = strlen(INPUT_TEXT3) },
+        };
+        uint8_t *result = NULL;
+        size_t resultlen = 0;
+        int ret;
+        size_t j;
+
+        ret = qcrypto_hash_bytesv(i,
+                                  iov, 3,
+                                  &result,
+                                  &resultlen,
+                                  NULL);
+        g_assert(ret == 0);
+        g_assert(resultlen == expected_lens[i]);
+        for (j = 0; j < resultlen; j++) {
+            g_assert(expected_outputs[i][j * 2] == hex[(result[j] >> 4) & 0xf]);
+            g_assert(expected_outputs[i][j * 2 + 1] == hex[result[j] & 0xf]);
+        }
+        g_free(result);
+    }
+}
+
+
+/* Test with printable hashing */
+static void test_hash_digest(void)
+{
+    size_t i;
+
+    g_assert(qcrypto_init(NULL) == 0);
+
+    for (i = 0; i < G_N_ELEMENTS(expected_outputs) ; i++) {
+        int ret;
+        char *digest;
+
+        ret = qcrypto_hash_digest(i,
+                                  INPUT_TEXT,
+                                  strlen(INPUT_TEXT),
+                                  &digest,
+                                  NULL);
+        g_assert(ret == 0);
+        g_assert(g_str_equal(digest, expected_outputs[i]));
+        g_free(digest);
+    }
+}
+
+/* Test with base64 encoding */
+static void test_hash_base64(void)
+{
+    size_t i;
+
+    g_assert(qcrypto_init(NULL) == 0);
+
+    for (i = 0; i < G_N_ELEMENTS(expected_outputs) ; i++) {
+        int ret;
+        char *digest;
+
+        ret = qcrypto_hash_base64(i,
+                                  INPUT_TEXT,
+                                  strlen(INPUT_TEXT),
+                                  &digest,
+                                  NULL);
+        g_assert(ret == 0);
+        g_assert(g_str_equal(digest, expected_outputs_b64[i]));
+        g_free(digest);
+    }
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+    g_test_add_func("/crypto/hash/iov", test_hash_iov);
+    g_test_add_func("/crypto/hash/alloc", test_hash_alloc);
+    g_test_add_func("/crypto/hash/prealloc", test_hash_prealloc);
+    g_test_add_func("/crypto/hash/digest", test_hash_digest);
+    g_test_add_func("/crypto/hash/base64", test_hash_base64);
+    return g_test_run();
+}
diff --git a/vl.c b/vl.c
index 8aac4ee..6902253 100644
--- a/vl.c
+++ b/vl.c
@@ -119,6 +119,7 @@ int main(int argc, char **argv)
 #include "qapi/opts-visitor.h"
 #include "qom/object_interfaces.h"
 #include "qapi-event.h"
+#include "crypto/init.h"
 
 #define DEFAULT_RAM_SIZE 128
 
@@ -2787,6 +2788,7 @@ int main(int argc, char **argv, char **envp)
     uint64_t ram_slots = 0;
     FILE *vmstate_dump_file = NULL;
     Error *main_loop_err = NULL;
+    Error *err = NULL;
 
     qemu_init_cpu_loop();
     qemu_mutex_lock_iothread();
@@ -2829,6 +2831,12 @@ int main(int argc, char **argv, char **envp)
 
     runstate_init();
 
+    if (qcrypto_init(&err) < 0) {
+        fprintf(stderr, "Cannot initialize crypto: %s\n",
+                error_get_pretty(err));
+        error_free(err);
+        exit(1);
+    }
     rtc_clock = QEMU_CLOCK_HOST;
 
     QLIST_INIT (&vm_change_state_head);
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 09/34] crypto: move built-in AES implementation into crypto/
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (7 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 08/34] crypto: introduce new module for computing hash digests Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 10/34] crypto: move built-in D3DES " Daniel P. Berrange
                   ` (25 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

To prepare for a generic internal cipher API, move the
built-in AES implementation into the crypto/ directory

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 block/qcow.c                   | 2 +-
 block/qcow2.c                  | 1 -
 block/qcow2.h                  | 2 +-
 crypto/Makefile.objs           | 1 +
 {util => crypto}/aes.c         | 2 +-
 include/{qemu => crypto}/aes.h | 0
 target-arm/crypto_helper.c     | 2 +-
 target-i386/fpu_helper.c       | 1 -
 target-i386/ops_sse.h          | 2 +-
 target-ppc/int_helper.c        | 2 +-
 util/Makefile.objs             | 2 +-
 11 files changed, 8 insertions(+), 9 deletions(-)
 rename {util => crypto}/aes.c (99%)
 rename include/{qemu => crypto}/aes.h (100%)

diff --git a/block/qcow.c b/block/qcow.c
index 0558969..cb07e8d 100644
--- a/block/qcow.c
+++ b/block/qcow.c
@@ -25,7 +25,7 @@
 #include "block/block_int.h"
 #include "qemu/module.h"
 #include <zlib.h>
-#include "qemu/aes.h"
+#include "crypto/aes.h"
 #include "migration/migration.h"
 
 /**************************************************************/
diff --git a/block/qcow2.c b/block/qcow2.c
index 316a8db..824bf52 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -25,7 +25,6 @@
 #include "block/block_int.h"
 #include "qemu/module.h"
 #include <zlib.h>
-#include "qemu/aes.h"
 #include "block/qcow2.h"
 #include "qemu/error-report.h"
 #include "qapi/qmp/qerror.h"
diff --git a/block/qcow2.h b/block/qcow2.h
index 422b825..3cf5318 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -25,7 +25,7 @@
 #ifndef BLOCK_QCOW2_H
 #define BLOCK_QCOW2_H
 
-#include "qemu/aes.h"
+#include "crypto/aes.h"
 #include "block/coroutine.h"
 
 //#define DEBUG_ALLOC
diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs
index 03cc1b2..9efc9b4 100644
--- a/crypto/Makefile.objs
+++ b/crypto/Makefile.objs
@@ -1,2 +1,3 @@
 util-obj-y += init.o
 util-obj-y += hash.o
+util-obj-y += aes.o
diff --git a/util/aes.c b/crypto/aes.c
similarity index 99%
rename from util/aes.c
rename to crypto/aes.c
index 3d7c4be..244a388 100644
--- a/util/aes.c
+++ b/crypto/aes.c
@@ -28,7 +28,7 @@
  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 #include "qemu-common.h"
-#include "qemu/aes.h"
+#include "crypto/aes.h"
 
 typedef uint32_t u32;
 typedef uint8_t u8;
diff --git a/include/qemu/aes.h b/include/crypto/aes.h
similarity index 100%
rename from include/qemu/aes.h
rename to include/crypto/aes.h
diff --git a/target-arm/crypto_helper.c b/target-arm/crypto_helper.c
index 1fe975d..5d22838 100644
--- a/target-arm/crypto_helper.c
+++ b/target-arm/crypto_helper.c
@@ -14,7 +14,7 @@
 #include "cpu.h"
 #include "exec/exec-all.h"
 #include "exec/helper-proto.h"
-#include "qemu/aes.h"
+#include "crypto/aes.h"
 
 union CRYPTO_STATE {
     uint8_t    bytes[16];
diff --git a/target-i386/fpu_helper.c b/target-i386/fpu_helper.c
index 30d34d5..280adba 100644
--- a/target-i386/fpu_helper.c
+++ b/target-i386/fpu_helper.c
@@ -20,7 +20,6 @@
 #include <math.h>
 #include "cpu.h"
 #include "exec/helper-proto.h"
-#include "qemu/aes.h"
 #include "qemu/host-utils.h"
 #include "exec/cpu_ldst.h"
 
diff --git a/target-i386/ops_sse.h b/target-i386/ops_sse.h
index 0765073..bee134b 100644
--- a/target-i386/ops_sse.h
+++ b/target-i386/ops_sse.h
@@ -18,7 +18,7 @@
  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "qemu/aes.h"
+#include "crypto/aes.h"
 
 #if SHIFT == 0
 #define Reg MMXReg
diff --git a/target-ppc/int_helper.c b/target-ppc/int_helper.c
index 4c2b71c..0a55d5e 100644
--- a/target-ppc/int_helper.c
+++ b/target-ppc/int_helper.c
@@ -19,7 +19,7 @@
 #include "cpu.h"
 #include "qemu/host-utils.h"
 #include "exec/helper-proto.h"
-#include "qemu/aes.h"
+#include "crypto/aes.h"
 
 #include "helper_regs.h"
 /*****************************************************************************/
diff --git a/util/Makefile.objs b/util/Makefile.objs
index ceaba30..114d657 100644
--- a/util/Makefile.objs
+++ b/util/Makefile.objs
@@ -9,7 +9,7 @@ util-obj-y += acl.o
 util-obj-y += error.o qemu-error.o
 util-obj-$(CONFIG_POSIX) += compatfd.o
 util-obj-y += id.o
-util-obj-y += iov.o aes.o qemu-config.o qemu-sockets.o uri.o notify.o
+util-obj-y += iov.o qemu-config.o qemu-sockets.o uri.o notify.o
 util-obj-y += qemu-option.o qemu-progress.o
 util-obj-y += hexdump.o
 util-obj-y += crc32c.o
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 10/34] crypto: move built-in D3DES implementation into crypto/
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (8 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 09/34] crypto: move built-in AES implementation into crypto/ Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 11/34] crypto: introduce generic cipher API & built-in implementation Daniel P. Berrange
                   ` (24 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

To prepare for a generic internal cipher API, move the
built-in D3DES implementation into the crypto/ directory.

This is not in fact a normal D3DES implementation, it is
D3DES with double & triple length modes removed, and the
key bytes in reversed bit order. IOW it is crippled
specifically for the "benefit" of RFB, so call the new
files desrfb.c instead of d3des.c to make it clear that
it isn't a generally useful impl.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 crypto/Makefile.objs                  | 1 +
 ui/d3des.c => crypto/desrfb.c         | 2 +-
 ui/d3des.h => include/crypto/desrfb.h | 0
 ui/Makefile.objs                      | 2 +-
 ui/vnc.c                              | 2 +-
 5 files changed, 4 insertions(+), 3 deletions(-)
 rename ui/d3des.c => crypto/desrfb.c (99%)
 rename ui/d3des.h => include/crypto/desrfb.h (100%)

diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs
index 9efc9b4..9f70294 100644
--- a/crypto/Makefile.objs
+++ b/crypto/Makefile.objs
@@ -1,3 +1,4 @@
 util-obj-y += init.o
 util-obj-y += hash.o
 util-obj-y += aes.o
+util-obj-y += desrfb.o
diff --git a/ui/d3des.c b/crypto/desrfb.c
similarity index 99%
rename from ui/d3des.c
rename to crypto/desrfb.c
index 5bc99b8..fc20a30 100644
--- a/ui/d3des.c
+++ b/crypto/desrfb.c
@@ -26,7 +26,7 @@
  * (GEnie : OUTER; CIS : [71755,204]) Graven Imagery, 1992.
  */
 
-#include "d3des.h"
+#include "crypto/desrfb.h"
 
 static void scrunch(unsigned char *, unsigned long *);
 static void unscrun(unsigned long *, unsigned char *);
diff --git a/ui/d3des.h b/include/crypto/desrfb.h
similarity index 100%
rename from ui/d3des.h
rename to include/crypto/desrfb.h
diff --git a/ui/Makefile.objs b/ui/Makefile.objs
index 13b5cfb..ca3ca38 100644
--- a/ui/Makefile.objs
+++ b/ui/Makefile.objs
@@ -1,4 +1,4 @@
-vnc-obj-y += vnc.o d3des.o
+vnc-obj-y += vnc.o
 vnc-obj-y += vnc-enc-zlib.o vnc-enc-hextile.o
 vnc-obj-y += vnc-enc-tight.o vnc-palette.o
 vnc-obj-y += vnc-enc-zrle.o
diff --git a/ui/vnc.c b/ui/vnc.c
index 6bfe228..39402d3 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -46,7 +46,7 @@ static const struct timeval VNC_REFRESH_STATS = { 0, 500000 };
 static const struct timeval VNC_REFRESH_LOSSY = { 2, 0 };
 
 #include "vnc_keysym.h"
-#include "d3des.h"
+#include "crypto/desrfb.h"
 
 static QTAILQ_HEAD(, VncDisplay) vnc_displays =
     QTAILQ_HEAD_INITIALIZER(vnc_displays);
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 11/34] crypto: introduce generic cipher API & built-in implementation
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (9 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 10/34] crypto: move built-in D3DES " Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 12/34] crypto: add a gcrypt cipher implementation Daniel P. Berrange
                   ` (23 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

Introduce a generic cipher API and an implementation of it that
supports only the built-in AES and DES-RFB algorithms.

The test suite checks the supported algorithms + modes to
validate that every backend implementation is actually correctly
complying with the specs.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 crypto/Makefile.objs       |   1 +
 crypto/cipher-builtin.c    | 391 +++++++++++++++++++++++++++++++++++++++++++++
 crypto/cipher.c            |  23 +++
 include/crypto/cipher.h    | 205 ++++++++++++++++++++++++
 tests/.gitignore           |   1 +
 tests/Makefile             |   2 +
 tests/test-crypto-cipher.c | 290 +++++++++++++++++++++++++++++++++
 7 files changed, 913 insertions(+)
 create mode 100644 crypto/cipher-builtin.c
 create mode 100644 crypto/cipher.c
 create mode 100644 include/crypto/cipher.h
 create mode 100644 tests/test-crypto-cipher.c

diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs
index 9f70294..b050138 100644
--- a/crypto/Makefile.objs
+++ b/crypto/Makefile.objs
@@ -2,3 +2,4 @@ util-obj-y += init.o
 util-obj-y += hash.o
 util-obj-y += aes.o
 util-obj-y += desrfb.o
+util-obj-y += cipher.o
diff --git a/crypto/cipher-builtin.c b/crypto/cipher-builtin.c
new file mode 100644
index 0000000..79b2b1d
--- /dev/null
+++ b/crypto/cipher-builtin.c
@@ -0,0 +1,391 @@
+/*
+ * QEMU Crypto cipher built-in algorithms
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/aes.h"
+#include "crypto/desrfb.h"
+
+#include <glib/gi18n.h>
+
+typedef struct QCryptoCipherAES QCryptoCipherAES;
+struct QCryptoCipherAES {
+    AES_KEY encrypt_key;
+    AES_KEY decrypt_key;
+    uint8_t *iv;
+    size_t niv;
+};
+
+static int qcrypto_cipher_init_aes(QCryptoCipher *cipher,
+                                   const uint8_t *key, size_t nkey,
+                                   Error **errp)
+{
+    QCryptoCipherAES *ctxt;
+
+    if (cipher->mode != QCRYPTO_CIPHER_MODE_CBC &&
+        cipher->mode != QCRYPTO_CIPHER_MODE_ECB) {
+        error_setg(errp, _("Unsupported cipher mode %d"), cipher->mode);
+        return -1;
+    }
+    if (!(nkey == 16 ||
+          nkey == 24 ||
+          nkey == 32)) {
+        error_setg(errp, _("Key must be 16, 24 or 32 bytes not %zu"), nkey);
+        return -1;
+    }
+
+    ctxt = g_new0(QCryptoCipherAES, 1);
+
+    if (AES_set_encrypt_key(key, nkey * 8, &ctxt->encrypt_key) != 0) {
+        error_setg(errp, "%s", _("Failed to set encryption key"));
+        goto error;
+    }
+
+    if (AES_set_decrypt_key(key, nkey * 8, &ctxt->decrypt_key) != 0) {
+        error_setg(errp, "%s", _("Failed to set decryption key"));
+        goto error;
+    }
+
+    cipher->opaque = ctxt;
+
+    return 0;
+
+ error:
+    g_free(ctxt);
+    return -1;
+}
+
+
+static void qcrypto_cipher_free_aes(QCryptoCipher *cipher)
+{
+    QCryptoCipherAES *ctxt = cipher->opaque;
+
+    g_free(ctxt->iv);
+    g_free(ctxt);
+    cipher->opaque = NULL;
+}
+
+
+static int qcrypto_cipher_encrypt_aes(QCryptoCipher *cipher,
+                                      const void *in,
+                                      void *out,
+                                      size_t len,
+                                      Error **errp)
+{
+    QCryptoCipherAES *ctxt = cipher->opaque;
+
+    if (cipher->mode == QCRYPTO_CIPHER_MODE_ECB) {
+        const uint8_t *inptr = in;
+        uint8_t *outptr = out;
+        while (len) {
+            if (len > AES_BLOCK_SIZE) {
+                AES_encrypt(inptr, outptr, &ctxt->encrypt_key);
+                inptr += AES_BLOCK_SIZE;
+                outptr += AES_BLOCK_SIZE;
+                len -= AES_BLOCK_SIZE;
+            } else {
+                uint8_t tmp1[AES_BLOCK_SIZE], tmp2[AES_BLOCK_SIZE];
+                memcpy(tmp1, inptr, len);
+                /* Fill with 0 to avoid valgrind uninitialized reads */
+                memset(tmp1 + len, 0, sizeof(tmp1) - len);
+                AES_encrypt(tmp1, tmp2, &ctxt->encrypt_key);
+                memcpy(outptr, tmp2, len);
+                len = 0;
+            }
+        }
+    } else {
+        AES_cbc_encrypt(in, out, len,
+                        &ctxt->encrypt_key,
+                        ctxt->iv, 1);
+    }
+
+    return 0;
+}
+
+
+static int qcrypto_cipher_decrypt_aes(QCryptoCipher *cipher,
+                                      const void *in,
+                                      void *out,
+                                      size_t len,
+                                      Error **errp)
+{
+    QCryptoCipherAES *ctxt = cipher->opaque;
+
+    if (cipher->mode == QCRYPTO_CIPHER_MODE_ECB) {
+        const uint8_t *inptr = in;
+        uint8_t *outptr = out;
+        while (len) {
+            if (len > AES_BLOCK_SIZE) {
+                AES_decrypt(inptr, outptr, &ctxt->encrypt_key);
+                inptr += AES_BLOCK_SIZE;
+                outptr += AES_BLOCK_SIZE;
+                len -= AES_BLOCK_SIZE;
+            } else {
+                uint8_t tmp1[AES_BLOCK_SIZE], tmp2[AES_BLOCK_SIZE];
+                memcpy(tmp1, inptr, len);
+                /* Fill with 0 to avoid valgrind uninitialized reads */
+                memset(tmp1 + len, 0, sizeof(tmp1) - len);
+                AES_decrypt(tmp1, tmp2, &ctxt->encrypt_key);
+                memcpy(outptr, tmp2, len);
+                len = 0;
+            }
+        }
+    } else {
+        AES_cbc_encrypt(in, out, len,
+                        &ctxt->encrypt_key,
+                        ctxt->iv, 1);
+    }
+
+    return 0;
+}
+
+static int qcrypto_cipher_setiv_aes(QCryptoCipher *cipher,
+                                     const uint8_t *iv, size_t niv,
+                                     Error **errp)
+{
+    QCryptoCipherAES *ctxt = cipher->opaque;
+    if (niv != 16) {
+        error_setg(errp, _("IV must be 16 bytes not %zu"), niv);
+        return -1;
+    }
+
+    g_free(ctxt->iv);
+    ctxt->iv = g_new0(uint8_t, niv);
+    memcpy(ctxt->iv, iv, niv);
+    ctxt->niv = niv;
+
+    return 0;
+}
+
+
+
+typedef struct QCryptoCipherDESRFB QCryptoCipherDESRFB;
+struct QCryptoCipherDESRFB {
+    uint8_t *key;
+    size_t nkey;
+};
+
+
+static int qcrypto_cipher_init_des_rfb(QCryptoCipher *cipher,
+                                       const uint8_t *key, size_t nkey,
+                                       Error **errp)
+{
+    QCryptoCipherDESRFB *ctxt;
+
+    if (cipher->mode != QCRYPTO_CIPHER_MODE_ECB) {
+        error_setg(errp, _("Unsupported cipher mode %d"), cipher->mode);
+        return -1;
+    }
+    if (nkey != 8) {
+        error_setg(errp, _("Key must be 8 bytes not %zu"), nkey);
+        return -1;
+    }
+
+    ctxt = g_new0(QCryptoCipherDESRFB, 1);
+
+    ctxt->key = g_new0(uint8_t, nkey);
+    memcpy(ctxt->key, key, nkey);
+    ctxt->nkey = nkey;
+
+    cipher->opaque = ctxt;
+
+    return 0;
+}
+
+
+static void qcrypto_cipher_free_des_rfb(QCryptoCipher *cipher)
+{
+    QCryptoCipherDESRFB *ctxt = cipher->opaque;
+
+    g_free(ctxt->key);
+    g_free(ctxt);
+    cipher->opaque = NULL;
+}
+
+static int qcrypto_cipher_encrypt_des_rfb(QCryptoCipher *cipher,
+                                          const void *in,
+                                          void *out,
+                                          size_t len,
+                                          Error **errp)
+{
+    QCryptoCipherDESRFB *ctxt = cipher->opaque;
+    size_t i;
+
+    deskey(ctxt->key, EN0);
+
+    if (len % 8) {
+        error_setg(errp, _("Buffer size must be multiple of 8 not %zu"),
+                   len);
+        return -1;
+    }
+
+    for (i = 0; i < len; i += 8) {
+        des((void *)in + i, out + i);
+    }
+
+    return 0;
+}
+
+static int qcrypto_cipher_decrypt_des_rfb(QCryptoCipher *cipher,
+                                          const void *in,
+                                          void *out,
+                                          size_t len,
+                                          Error **errp)
+{
+    QCryptoCipherDESRFB *ctxt = cipher->opaque;
+    size_t i;
+
+    deskey(ctxt->key, DE1);
+
+    if (len % 8) {
+        error_setg(errp, _("Buffer size must be multiple of 8 not %zu"),
+                   len);
+        return -1;
+    }
+
+    for (i = 0; i < len; i += 8) {
+        des((void *)in + i, out + i);
+    }
+
+    return 0;
+}
+
+static int qcrypto_cipher_setiv_des_rfb(QCryptoCipher *cipher,
+                                        const uint8_t *iv, size_t niv,
+                                        Error **errp)
+{
+    error_setg(errp, "%s", _("Setting IV is not supported"));
+    return -1;
+}
+
+
+bool qcrypto_cipher_supports(QCryptoCipherAlgorithm alg)
+{
+    if (alg == QCRYPTO_CIPHER_ALG_DES_RFB ||
+        alg == QCRYPTO_CIPHER_ALG_AES) {
+        return true;
+    }
+    return false;
+}
+
+
+QCryptoCipher *qcrypto_cipher_new(QCryptoCipherAlgorithm alg,
+                                  QCryptoCipherMode mode,
+                                  const uint8_t *key, size_t nkey,
+                                  Error **errp)
+{
+    QCryptoCipher *cipher;
+
+    cipher = g_new0(QCryptoCipher, 1);
+    cipher->alg = alg;
+    cipher->mode = mode;
+
+    switch (cipher->alg) {
+    case QCRYPTO_CIPHER_ALG_DES_RFB:
+        if (qcrypto_cipher_init_des_rfb(cipher, key, nkey, errp) < 0) {
+            goto error;
+        }
+        break;
+    case QCRYPTO_CIPHER_ALG_AES:
+        if (qcrypto_cipher_init_aes(cipher, key, nkey, errp) < 0) {
+            goto error;
+        }
+        break;
+    default:
+        error_setg(errp,
+                   _("Unsupported cipher algorithm %d"), cipher->alg);
+        goto error;
+    }
+
+    return cipher;
+
+ error:
+    g_free(cipher);
+    return NULL;
+}
+
+void qcrypto_cipher_free(QCryptoCipher *cipher)
+{
+    if (!cipher) {
+        return;
+    }
+
+    switch (cipher->alg) {
+    case QCRYPTO_CIPHER_ALG_DES_RFB:
+        qcrypto_cipher_free_des_rfb(cipher);
+        break;
+    case QCRYPTO_CIPHER_ALG_AES:
+        qcrypto_cipher_free_aes(cipher);
+        break;
+    default:
+        break;
+    }
+    g_free(cipher);
+}
+
+int qcrypto_cipher_encrypt(QCryptoCipher *cipher,
+                           const void *in,
+                           void *out,
+                           size_t len,
+                           Error **errp)
+{
+    switch (cipher->alg) {
+    case QCRYPTO_CIPHER_ALG_DES_RFB:
+        return qcrypto_cipher_encrypt_des_rfb(cipher, in, out, len, errp);
+    case QCRYPTO_CIPHER_ALG_AES:
+        return qcrypto_cipher_encrypt_aes(cipher, in, out, len, errp);
+    default:
+        error_setg(errp,
+                   _("Unsupported cipher algorithm %d"), cipher->alg);
+        return -1;
+    }
+}
+
+int qcrypto_cipher_decrypt(QCryptoCipher *cipher,
+                           const void *in,
+                           void *out,
+                           size_t len,
+                           Error **errp)
+{
+    switch (cipher->alg) {
+    case QCRYPTO_CIPHER_ALG_DES_RFB:
+        return qcrypto_cipher_decrypt_des_rfb(cipher, in, out, len, errp);
+    case QCRYPTO_CIPHER_ALG_AES:
+        return qcrypto_cipher_decrypt_aes(cipher, in, out, len, errp);
+    default:
+        error_setg(errp,
+                   _("Unsupported cipher algorithm %d"), cipher->alg);
+        return -1;
+    }
+}
+
+int qcrypto_cipher_setiv(QCryptoCipher *cipher,
+                         const uint8_t *iv, size_t niv,
+                         Error **errp)
+{
+    switch (cipher->alg) {
+    case QCRYPTO_CIPHER_ALG_DES_RFB:
+        return qcrypto_cipher_setiv_des_rfb(cipher, iv, niv, errp);
+    case QCRYPTO_CIPHER_ALG_AES:
+        return qcrypto_cipher_setiv_aes(cipher, iv, niv, errp);
+    default:
+        error_setg(errp,
+                   _("Unsupported cipher algorithm %d"), cipher->alg);
+        return -1;
+    }
+}
diff --git a/crypto/cipher.c b/crypto/cipher.c
new file mode 100644
index 0000000..71e9eae
--- /dev/null
+++ b/crypto/cipher.c
@@ -0,0 +1,23 @@
+/*
+ * QEMU Crypto cipher algorithms
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/cipher.h"
+
+#include "crypto/cipher-builtin.c"
diff --git a/include/crypto/cipher.h b/include/crypto/cipher.h
new file mode 100644
index 0000000..9def8d5
--- /dev/null
+++ b/include/crypto/cipher.h
@@ -0,0 +1,205 @@
+/*
+ * QEMU Crypto cipher algorithms
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QCRYPTO_CIPHER_H__
+#define QCRYPTO_CIPHER_H__
+
+#include "qemu-common.h"
+#include "qapi/error.h"
+
+typedef struct QCryptoCipher QCryptoCipher;
+
+typedef enum {
+    QCRYPTO_CIPHER_ALG_AES,
+    QCRYPTO_CIPHER_ALG_DES_RFB, /* A stupid variant on DES for VNC */
+
+    QCRYPTO_CIPHER_ALG_LAST
+} QCryptoCipherAlgorithm;
+
+typedef enum {
+    QCRYPTO_CIPHER_MODE_ECB,
+    QCRYPTO_CIPHER_MODE_CBC,
+
+    QCRYPTO_CIPHER_MODE_LAST
+} QCryptoCipherMode;
+
+/**
+ * QCryptoCipher:
+ *
+ * The QCryptoCipher object provides a way to perform encryption
+ * and decryption of data, with a standard API, regardless of the
+ * algorithm used. It further isolates the calling code from the
+ * details of the specific underlying implementation, whether
+ * built-in, libgcrypt or nettle.
+ *
+ * Each QCryptoCipher object is capable of performing both
+ * encryption and decryption, and can operate in a number
+ * or modes including ECB, CBC.
+ *
+ * Typical usage would be
+ *
+ * QCryptoCipher *cipher;
+ * uint8_t key = ....;
+ * size_t keylen = 16;
+ * uint8_t iv = ....;
+ *
+ * if (!qcrypto_cipher_supports(QCRYPTO_CIPHER_ALG_AES)) {
+ *    error_report(errp, "Feature <blah> requires AES cipher support");
+ *    return -1;
+ * }
+ *
+ * cipher = qcrypto_cipher_new(QCRYPTO_CIPHER_ALG_AES,
+ *                             QCRYPTO_CIPHER_MODE_CBC,
+ *                             key, keylen,
+ *                             errp);
+ * if (!cipher) {
+ *    return -1;
+ * }
+ *
+ * if (qcrypto_cipher_set_iv(cipher, iv, keylen, errp) < 0) {
+ *    return -1;
+ * }
+ *
+ * if (qcrypto_cipher_encrypt(cipher, rawdata, encdata, datalen, errp) < 0) {
+ *    return -1;
+ * }
+ *
+ * qcrypto_cipher_free(cipher);
+ *
+ */
+
+struct QCryptoCipher {
+    QCryptoCipherAlgorithm alg;
+    QCryptoCipherMode mode;
+    void *opaque;
+};
+
+/**
+ * qcrypto_cipher_supports:
+ * @alg: the cipher algorithm
+ *
+ * Determine if @alg cipher algorithm is supported by the
+ * current configured build
+ *
+ * Returns: true if the algorithm is supported, false otherwise
+ */
+bool qcrypto_cipher_supports(QCryptoCipherAlgorithm alg);
+
+
+/**
+ * qcrypto_cipher_new:
+ * @alg: the cipher algorithm
+ * @mode: the cipher usage mode
+ * @key: the private key bytes
+ * @nkey: the length of @key
+ * @errp: pointer to an uninitialized error object
+ *
+ * Creates a new cipher object for encrypting/decrypting
+ * data with the algorithm @alg in the usage mode @mode.
+ *
+ * The @key parameter provides the bytes representing
+ * the encryption/decryption key to use. The @nkey parameter
+ * specifies the length of @key in bytes. Each algorithm has
+ * one or more valid key lengths, and it is an error to provide
+ * a key of the incorrect length.
+ *
+ * The returned cipher object must be released with
+ * qcrypto_cipher_free() when no longer required
+ *
+ * Returns: a new cipher object, or NULL on error
+ */
+QCryptoCipher *qcrypto_cipher_new(QCryptoCipherAlgorithm alg,
+                                  QCryptoCipherMode mode,
+                                  const uint8_t *key, size_t nkey,
+                                  Error **errp);
+
+/**
+ * qcrypto_cipher_free:
+ * @cipher: the cipher object
+ *
+ * Release the memory associated with @cipher that
+ * was previously allocated by qcrypto_cipher_new()
+ */
+void qcrypto_cipher_free(QCryptoCipher *cipher);
+
+/**
+ * qcrypto_cipher_encrypt:
+ * @cipher: the cipher object
+ * @in: buffer holding the plain text input data
+ * @out: buffer to fill with the cipher text output data
+ * @len: the length of @in and @out buffers
+ * @errp: pointer to an uninitialized error object
+ *
+ * Encrypts the plain text stored in @in, filling
+ * @out with the resulting ciphered text. Both the
+ * @in and @out buffers must have the same size,
+ * given by @len.
+ *
+ * Returns: 0 on success, or -1 on error
+ */
+int qcrypto_cipher_encrypt(QCryptoCipher *cipher,
+                           const void *in,
+                           void *out,
+                           size_t len,
+                           Error **errp);
+
+
+/**
+ * qcrypto_cipher_decrypt:
+ * @cipher: the cipher object
+ * @in: buffer holding the cipher text input data
+ * @out: buffer to fill with the plain text output data
+ * @len: the length of @in and @out buffers
+ * @errp: pointer to an uninitialized error object
+ *
+ * Decrypts the cipher text stored in @in, filling
+ * @out with the resulting plain text. Both the
+ * @in and @out buffers must have the same size,
+ * given by @len.
+ *
+ * Returns: 0 on success, or -1 on error
+ */
+int qcrypto_cipher_decrypt(QCryptoCipher *cipher,
+                           const void *in,
+                           void *out,
+                           size_t len,
+                           Error **errp);
+
+/**
+ * qcrypto_cipher_setiv:
+ * @cipher: the cipher object
+ * @iv: the initialization vector bytes
+ * @niv: the length of @iv
+ * @errpr: pointer to an uninitialized error object
+ *
+ * If the @cipher object is setup to use a mode that requires
+ * initialization vectors, this sets the initialization vector
+ * bytes. The @iv data should have the same length as the
+ * cipher key used when originally constructing the cipher
+ * object. It is an error to set an initialization vector
+ * if the cipher mode does not require one.
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int qcrypto_cipher_setiv(QCryptoCipher *cipher,
+                         const uint8_t *iv, size_t niv,
+                         Error **errp);
+
+#endif /* QCRYPTO_CIPHER_H__ */
diff --git a/tests/.gitignore b/tests/.gitignore
index 12d2373..e93148e 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -9,6 +9,7 @@ rcutorture
 test-aio
 test-bitops
 test-coroutine
+test-crypto-cipher
 test-crypto-hash
 test-cutils
 test-hbitmap
diff --git a/tests/Makefile b/tests/Makefile
index ba7d64e..252de54 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -73,6 +73,7 @@ gcov-files-test-qemu-opts-y = qom/test-qemu-opts.c
 check-unit-y += tests/test-write-threshold$(EXESUF)
 gcov-files-test-write-threshold-y = block/write-threshold.c
 check-unit-$(CONFIG_GNUTLS_HASH) += tests/test-crypto-hash$(EXESUF)
+check-unit-y += tests/test-crypto-cipher$(EXESUF)
 
 check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
 
@@ -311,6 +312,7 @@ tests/test-opts-visitor$(EXESUF): tests/test-opts-visitor.o $(test-qapi-obj-y) l
 tests/test-mul64$(EXESUF): tests/test-mul64.o libqemuutil.a
 tests/test-bitops$(EXESUF): tests/test-bitops.o libqemuutil.a
 tests/test-crypto-hash$(EXESUF): tests/test-crypto-hash.o libqemuutil.a libqemustub.a
+tests/test-crypto-cipher$(EXESUF): tests/test-crypto-cipher.o libqemuutil.a libqemustub.a
 
 libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o
 libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o
diff --git a/tests/test-crypto-cipher.c b/tests/test-crypto-cipher.c
new file mode 100644
index 0000000..80d1dee
--- /dev/null
+++ b/tests/test-crypto-cipher.c
@@ -0,0 +1,290 @@
+/*
+ * QEMU Crypto cipher algorithms
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <glib.h>
+
+#include "crypto/init.h"
+#include "crypto/cipher.h"
+
+typedef struct QCryptoCipherTestData QCryptoCipherTestData;
+struct QCryptoCipherTestData {
+    const char *path;
+    QCryptoCipherAlgorithm alg;
+    QCryptoCipherMode mode;
+    const char *key;
+    const char *plaintext;
+    const char *ciphertext;
+    const char *iv;
+};
+
+/* AES test data comes from appendix F of:
+ *
+ * http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
+ */
+static QCryptoCipherTestData test_data[] = {
+    {
+        /* NIST F.1.1 ECB-AES128.Encrypt */
+        .path = "/crypto/cipher/aes-ecb-128",
+        .alg = QCRYPTO_CIPHER_ALG_AES,
+        .mode = QCRYPTO_CIPHER_MODE_ECB,
+        .key = "2b7e151628aed2a6abf7158809cf4f3c",
+        .plaintext =
+            "6bc1bee22e409f96e93d7e117393172a"
+            "ae2d8a571e03ac9c9eb76fac45af8e51"
+            "30c81c46a35ce411e5fbc1191a0a52ef"
+            "f69f2445df4f9b17ad2b417be66c3710",
+        .ciphertext =
+            "3ad77bb40d7a3660a89ecaf32466ef97"
+            "f5d3d58503b9699de785895a96fdbaaf"
+            "43b1cd7f598ece23881b00e3ed030688"
+            "7b0c785e27e8ad3f8223207104725dd4"
+    },
+    {
+        /* NIST F.1.3 ECB-AES192.Encrypt */
+        .path = "/crypto/cipher/aes-ecb-192",
+        .alg = QCRYPTO_CIPHER_ALG_AES,
+        .mode = QCRYPTO_CIPHER_MODE_ECB,
+        .key = "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b",
+        .plaintext  =
+            "6bc1bee22e409f96e93d7e117393172a"
+            "ae2d8a571e03ac9c9eb76fac45af8e51"
+            "30c81c46a35ce411e5fbc1191a0a52ef"
+            "f69f2445df4f9b17ad2b417be66c3710",
+        .ciphertext =
+            "bd334f1d6e45f25ff712a214571fa5cc"
+            "974104846d0ad3ad7734ecb3ecee4eef"
+            "ef7afd2270e2e60adce0ba2face6444e"
+            "9a4b41ba738d6c72fb16691603c18e0e"
+    },
+    {
+        /* NIST F.1.5 ECB-AES256.Encrypt */
+        .path = "/crypto/cipher/aes-ecb-256",
+        .alg = QCRYPTO_CIPHER_ALG_AES,
+        .mode = QCRYPTO_CIPHER_MODE_ECB,
+        .key =
+            "603deb1015ca71be2b73aef0857d7781"
+            "1f352c073b6108d72d9810a30914dff4",
+        .plaintext  =
+            "6bc1bee22e409f96e93d7e117393172a"
+            "ae2d8a571e03ac9c9eb76fac45af8e51"
+            "30c81c46a35ce411e5fbc1191a0a52ef"
+            "f69f2445df4f9b17ad2b417be66c3710",
+        .ciphertext =
+            "f3eed1bdb5d2a03c064b5a7e3db181f8"
+            "591ccb10d410ed26dc5ba74a31362870"
+            "b6ed21b99ca6f4f9f153e7b1beafed1d"
+            "23304b7a39f9f3ff067d8d8f9e24ecc7",
+    },
+    {
+        /* NIST F.2.1 CBC-AES128.Encrypt */
+        .path = "/crypto/cipher/aes-cbc-128",
+        .alg = QCRYPTO_CIPHER_ALG_AES,
+        .mode = QCRYPTO_CIPHER_MODE_CBC,
+        .key = "2b7e151628aed2a6abf7158809cf4f3c",
+        .iv = "000102030405060708090a0b0c0d0e0f",
+        .plaintext  =
+            "6bc1bee22e409f96e93d7e117393172a"
+            "ae2d8a571e03ac9c9eb76fac45af8e51"
+            "30c81c46a35ce411e5fbc1191a0a52ef"
+            "f69f2445df4f9b17ad2b417be66c3710",
+        .ciphertext =
+            "7649abac8119b246cee98e9b12e9197d"
+            "5086cb9b507219ee95db113a917678b2"
+            "73bed6b8e3c1743b7116e69e22229516"
+            "3ff1caa1681fac09120eca307586e1a7",
+    },
+    {
+        /* NIST F.2.3 CBC-AES128.Encrypt */
+        .path = "/crypto/cipher/aes-cbc-192",
+        .alg = QCRYPTO_CIPHER_ALG_AES,
+        .mode = QCRYPTO_CIPHER_MODE_CBC,
+        .key = "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b",
+        .iv = "000102030405060708090a0b0c0d0e0f",
+        .plaintext  =
+            "6bc1bee22e409f96e93d7e117393172a"
+            "ae2d8a571e03ac9c9eb76fac45af8e51"
+            "30c81c46a35ce411e5fbc1191a0a52ef"
+            "f69f2445df4f9b17ad2b417be66c3710",
+        .ciphertext =
+            "4f021db243bc633d7178183a9fa071e8"
+            "b4d9ada9ad7dedf4e5e738763f69145a"
+            "571b242012fb7ae07fa9baac3df102e0"
+            "08b0e27988598881d920a9e64f5615cd",
+    },
+    {
+        /* NIST F.2.5 CBC-AES128.Encrypt */
+        .path = "/crypto/cipher/aes-cbc-256",
+        .alg = QCRYPTO_CIPHER_ALG_AES,
+        .mode = QCRYPTO_CIPHER_MODE_CBC,
+        .key =
+            "603deb1015ca71be2b73aef0857d7781"
+            "1f352c073b6108d72d9810a30914dff4",
+        .iv = "000102030405060708090a0b0c0d0e0f",
+        .plaintext  =
+            "6bc1bee22e409f96e93d7e117393172a"
+            "ae2d8a571e03ac9c9eb76fac45af8e51"
+            "30c81c46a35ce411e5fbc1191a0a52ef"
+            "f69f2445df4f9b17ad2b417be66c3710",
+        .ciphertext =
+            "f58c4c04d6e5f1ba779eabfb5f7bfbd6"
+            "9cfc4e967edb808d679f777bc6702c7d"
+            "39f23369a9d9bacfa530e26304231461"
+            "b2eb05e2c39be9fcda6c19078c6a9d1b",
+    },
+    {
+        .path = "/crypto/cipher/des-rfb-ecb-56",
+        .alg = QCRYPTO_CIPHER_ALG_DES_RFB,
+        .mode = QCRYPTO_CIPHER_MODE_ECB,
+        .key = "0123456789abcdef",
+        .plaintext =
+            "6bc1bee22e409f96e93d7e117393172a"
+            "ae2d8a571e03ac9c9eb76fac45af8e51"
+            "30c81c46a35ce411e5fbc1191a0a52ef"
+            "f69f2445df4f9b17ad2b417be66c3710",
+        .ciphertext =
+            "8f346aaf64eaf24040720d80648c52e7"
+            "aefc616be53ab1a3d301e69d91e01838"
+            "ffd29f1bb5596ad94ea2d8e6196b7f09"
+            "30d8ed0bf2773af36dd82a6280c20926",
+    },
+};
+
+
+static inline int unhex(char c)
+{
+    if (c >= 'a' && c <= 'f') {
+        return 10 + (c - 'a');
+    }
+    if (c >= 'A' && c <= 'F') {
+        return 10 + (c - 'A');
+    }
+    return c - '0';
+}
+
+static inline char hex(int i)
+{
+    if (i < 10) {
+        return '0' + i;
+    }
+    return 'a' + (i - 10);
+}
+
+static size_t unhex_string(const char *hexstr,
+                           uint8_t **data)
+{
+    size_t len;
+    size_t i;
+
+    if (!hexstr) {
+        *data = NULL;
+        return 0;
+    }
+
+    len = strlen(hexstr);
+    *data = g_new0(uint8_t, len / 2);
+
+    for (i = 0; i < len; i += 2) {
+        (*data)[i/2] = (unhex(hexstr[i]) << 4) | unhex(hexstr[i+1]);
+    }
+    return len / 2;
+}
+
+static char *hex_string(const uint8_t *bytes,
+                        size_t len)
+{
+    char *hexstr = g_new0(char, len * 2 + 1);
+    size_t i;
+
+    for (i = 0; i < len; i++) {
+        hexstr[i*2] = hex((bytes[i] >> 4) & 0xf);
+        hexstr[i*2+1] = hex(bytes[i] & 0xf);
+    }
+    hexstr[len*2] = '\0';
+
+    return hexstr;
+}
+
+static void test_cipher(const void *opaque)
+{
+    const QCryptoCipherTestData *data = opaque;
+
+    QCryptoCipher *cipher;
+    Error *err = NULL;
+    uint8_t *key, *iv, *ciphertext, *plaintext, *outtext;
+    size_t nkey, niv, nciphertext, nplaintext;
+    char *outtexthex;
+
+    g_test_message("foo");
+    nkey = unhex_string(data->key, &key);
+    niv = unhex_string(data->iv, &iv);
+    nciphertext = unhex_string(data->ciphertext, &ciphertext);
+    nplaintext = unhex_string(data->plaintext, &plaintext);
+
+    g_assert(nciphertext == nplaintext);
+
+    outtext = g_new0(uint8_t, nciphertext);
+
+    cipher = qcrypto_cipher_new(
+        data->alg, data->mode,
+        key, nkey,
+        &err);
+    g_assert(cipher != NULL);
+    g_assert(err == NULL);
+
+
+    if (iv) {
+        g_assert(qcrypto_cipher_setiv(cipher,
+                                      iv, niv,
+                                      &err) == 0);
+        g_assert(err == NULL);
+    }
+    g_assert(qcrypto_cipher_encrypt(cipher,
+                                    plaintext,
+                                    outtext,
+                                    nplaintext,
+                                    &err) == 0);
+    g_assert(err == NULL);
+
+    outtexthex = hex_string(outtext, nciphertext);
+
+    g_assert_cmpstr(outtexthex, ==, data->ciphertext);
+
+    g_free(outtext);
+    g_free(outtexthex);
+    g_free(key);
+    g_free(iv);
+    g_free(ciphertext);
+    g_free(plaintext);
+    qcrypto_cipher_free(cipher);
+}
+
+int main(int argc, char **argv)
+{
+    size_t i;
+
+    g_test_init(&argc, &argv, NULL);
+
+    g_assert(qcrypto_init(NULL) == 0);
+
+    for (i = 0; i < G_N_ELEMENTS(test_data); i++) {
+        g_test_add_data_func(test_data[i].path, &test_data[i], test_cipher);
+    }
+    return g_test_run();
+}
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 12/34] crypto: add a gcrypt cipher implementation
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (10 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 11/34] crypto: introduce generic cipher API & built-in implementation Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 13/34] crypto: add a nettle " Daniel P. Berrange
                   ` (22 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

If we are linking to gnutls already and gnutls is built against
gcrypt, then we should use gcrypt as a cipher backend in
preference to our built-in backend.

This will be used when linking against GNUTLS 1.x and many
GNUTLS 2.x versions.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 configure              |  29 +++++++
 crypto/cipher-gcrypt.c | 204 +++++++++++++++++++++++++++++++++++++++++++++++++
 crypto/cipher.c        |   4 +
 crypto/init.c          |  90 ++++++++++++++++++++++
 4 files changed, 327 insertions(+)
 create mode 100644 crypto/cipher-gcrypt.c

diff --git a/configure b/configure
index 48b4491..444e165 100755
--- a/configure
+++ b/configure
@@ -2144,6 +2144,7 @@ fi
 ##########################################
 # GNUTLS probe
 
+gnutls_gcrypt=no
 if test "$gnutls" != "no"; then
     if $pkg_config --exists "gnutls"; then
         gnutls_cflags=`$pkg_config --cflags gnutls`
@@ -2159,6 +2160,18 @@ if test "$gnutls" != "no"; then
 	else
 	    gnutls_hash="no"
 	fi
+
+	if $pkg_config --exists 'gnutls >= 3.0'; then
+	    gnutls_gcrypt=no
+	elif $pkg_config --exists 'gnutls >= 2.12'; then
+	    case `$pkg_config --libs --static gnutls` in
+		*gcrypt*) gnutls_gcrypt=yes     ;;
+		*nettle*) gnutls_gcrypt=no      ;;
+		*)        gnutls_gcrypt=yes     ;;
+	    esac
+	else
+	    gnutls_gcrypt=yes
+	fi
     elif test "$gnutls" = "yes"; then
 	feature_not_found "gnutls" "Install gnutls devel"
     else
@@ -2169,6 +2182,18 @@ else
     gnutls_hash="no"
 fi
 
+if test "$gnutls_gcrypt" != "no"; then
+    if has "libgcrypt-config"; then
+        gcrypt_cflags=`libgcrypt-config --cflags`
+        gcrypt_libs=`libgcrypt-config --libs`
+        libs_softmmu="$gcrypt_libs $libs_softmmu"
+        libs_tools="$gcrypt_libs $libs_tools"
+	QEMU_CFLAGS="$QEMU_CFLAGS $gcrypt_cflags"
+    else
+	feature_not_found "gcrypt" "Install gcrypt devel"
+    fi
+fi
+
 
 ##########################################
 # VTE probe
@@ -4399,6 +4424,7 @@ echo "SDL support       $sdl"
 echo "GTK support       $gtk"
 echo "GNUTLS support    $gnutls"
 echo "GNUTLS hash       $gnutls_hash"
+echo "GNUTLS gcrypt     $gnutls_gcrypt"
 echo "VTE support       $vte"
 echo "curses support    $curses"
 echo "curl support      $curl"
@@ -4756,6 +4782,9 @@ fi
 if test "$gnutls_hash" = "yes" ; then
   echo "CONFIG_GNUTLS_HASH=y" >> $config_host_mak
 fi
+if test "$gnutls_gcrypt" = "yes" ; then
+  echo "CONFIG_GNUTLS_GCRYPT=y" >> $config_host_mak
+fi
 if test "$vte" = "yes" ; then
   echo "CONFIG_VTE=y" >> $config_host_mak
   echo "VTE_CFLAGS=$vte_cflags" >> $config_host_mak
diff --git a/crypto/cipher-gcrypt.c b/crypto/cipher-gcrypt.c
new file mode 100644
index 0000000..cf3832f
--- /dev/null
+++ b/crypto/cipher-gcrypt.c
@@ -0,0 +1,204 @@
+/*
+ * QEMU Crypto cipher libgcrypt algorithms
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <glib/gi18n.h>
+#include <gcrypt.h>
+
+
+static uint8_t *qcrypto_cipher_munge_des_rfb_key(const uint8_t *key,
+                                                 size_t nkey)
+{
+    uint8_t *ret = g_new0(uint8_t, nkey);
+    size_t i;
+    for (i = 0; i < nkey; i++) {
+        uint8_t r = key[i];
+        r = (r & 0xf0) >> 4 | (r & 0x0f) << 4;
+        r = (r & 0xcc) >> 2 | (r & 0x33) << 2;
+        r = (r & 0xaa) >> 1 | (r & 0x55) << 1;
+        ret[i] = r;
+    }
+    return ret;
+}
+
+bool qcrypto_cipher_supports(QCryptoCipherAlgorithm alg)
+{
+    if (alg == QCRYPTO_CIPHER_ALG_DES_RFB ||
+        alg == QCRYPTO_CIPHER_ALG_AES) {
+        return true;
+    }
+    return false;
+}
+
+
+QCryptoCipher *qcrypto_cipher_new(QCryptoCipherAlgorithm alg,
+                                  QCryptoCipherMode mode,
+                                  const uint8_t *key, size_t nkey,
+                                  Error **errp)
+{
+    QCryptoCipher *cipher;
+    gcry_cipher_hd_t handle;
+    gcry_error_t err;
+    int gcryalg, gcrymode;
+
+    switch (mode) {
+    case QCRYPTO_CIPHER_MODE_ECB:
+        gcrymode = GCRY_CIPHER_MODE_ECB;
+        break;
+    case QCRYPTO_CIPHER_MODE_CBC:
+        gcrymode = GCRY_CIPHER_MODE_CBC;
+        break;
+    default:
+        error_setg(errp, _("Unsupported cipher mode %d"), mode);
+        return NULL;
+    }
+
+    switch (alg) {
+    case QCRYPTO_CIPHER_ALG_DES_RFB:
+        gcryalg = GCRY_CIPHER_DES;
+        break;
+
+    case QCRYPTO_CIPHER_ALG_AES:
+        if (nkey == 16) {
+            gcryalg = GCRY_CIPHER_AES128;
+        } else if (nkey == 24) {
+            gcryalg = GCRY_CIPHER_AES192;
+        } else if (nkey == 32) {
+            gcryalg = GCRY_CIPHER_AES256;
+        } else {
+            error_setg(errp, _("Expected key size 16, 24 or 32 not %zu"),
+                       nkey);
+            return NULL;
+        }
+        break;
+
+    default:
+        error_setg(errp, _("Unsupported cipher algorithm %d"), alg);
+        return NULL;
+    }
+
+    cipher = g_new0(QCryptoCipher, 1);
+    cipher->alg = alg;
+    cipher->mode = mode;
+
+    err = gcry_cipher_open(&handle, gcryalg, gcrymode, 0);
+    if (err != 0) {
+        error_setg(errp, _("Cannot initialize cipher: %s"),
+                   gcry_strerror(err));
+        goto error;
+    }
+
+    if (cipher->alg == QCRYPTO_CIPHER_ALG_DES_RFB) {
+        /* We're using standard DES cipher from gcrypt, so we need
+         * to munge the key so that the results are the same as the
+         * bizarre RFB variant of DES :-)
+         */
+        uint8_t *rfbkey = qcrypto_cipher_munge_des_rfb_key(key, nkey);
+        err = gcry_cipher_setkey(handle, rfbkey, nkey);
+        g_free(rfbkey);
+    } else {
+        err = gcry_cipher_setkey(handle, key, nkey);
+    }
+    if (err != 0) {
+        error_setg(errp, _("Cannot set key: %s"),
+                   gcry_strerror(err));
+        goto error;
+    }
+
+    cipher->opaque = handle;
+    return cipher;
+
+ error:
+    g_printerr("aile %s\n", error_get_pretty(*errp));
+    gcry_cipher_close(handle);
+    g_free(cipher);
+    return NULL;
+}
+
+
+void qcrypto_cipher_free(QCryptoCipher *cipher)
+{
+    if (!cipher) {
+        return;
+    }
+    gcry_cipher_close(cipher->opaque);
+    g_free(cipher);
+}
+
+
+int qcrypto_cipher_encrypt(QCryptoCipher *cipher,
+                           const void *in,
+                           void *out,
+                           size_t len,
+                           Error **errp)
+{
+    gcry_cipher_hd_t handle = cipher->opaque;
+    gcry_error_t err;
+
+    err = gcry_cipher_encrypt(handle,
+                              out, len,
+                              in, len);
+    if (err != 0) {
+        error_setg(errp, _("Cannot encrypt data: %s"),
+                   gcry_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+
+int qcrypto_cipher_decrypt(QCryptoCipher *cipher,
+                           const void *in,
+                           void *out,
+                           size_t len,
+                           Error **errp)
+{
+    gcry_cipher_hd_t handle = cipher->opaque;
+    gcry_error_t err;
+
+    err = gcry_cipher_decrypt(handle,
+                              out, len,
+                              in, len);
+    if (err != 0) {
+        error_setg(errp, _("Cannot decrypt data: %s"),
+                   gcry_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+int qcrypto_cipher_setiv(QCryptoCipher *cipher,
+                         const uint8_t *iv, size_t niv,
+                         Error **errp)
+{
+    gcry_cipher_hd_t handle = cipher->opaque;
+    gcry_error_t err;
+
+    gcry_cipher_reset(handle);
+    err = gcry_cipher_setiv(handle, iv, niv);
+    if (err != 0) {
+        error_setg(errp, _("Cannot set IV: %s"),
+                   gcry_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/crypto/cipher.c b/crypto/cipher.c
index 71e9eae..dc140fb 100644
--- a/crypto/cipher.c
+++ b/crypto/cipher.c
@@ -20,4 +20,8 @@
 
 #include "crypto/cipher.h"
 
+#ifdef CONFIG_GNUTLS_GCRYPT
+#include "crypto/cipher-gcrypt.c"
+#else
 #include "crypto/cipher-builtin.c"
+#endif
diff --git a/crypto/init.c b/crypto/init.c
index 8fd66d4..486af37 100644
--- a/crypto/init.c
+++ b/crypto/init.c
@@ -19,6 +19,7 @@
  */
 
 #include "crypto/init.h"
+#include "qemu/thread.h"
 
 #include <glib/gi18n.h>
 
@@ -26,8 +27,42 @@
 #include <gnutls/gnutls.h>
 #include <gnutls/crypto.h>
 
+#ifdef CONFIG_GNUTLS_GCRYPT
+#include <gcrypt.h>
+#endif
+
 /* #define DEBUG_GNUTLS */
 
+/*
+ * If GNUTLS is built against GCrypt then
+ *
+ *  - When GNUTLS >= 2.12, we must not initialize gcrypt threading
+ *    because GNUTLS will do that itself
+ *  - When GNUTLS < 2.12 we must always initialize gcrypt threading
+ *
+ * But....
+ *
+ *    When gcrypt >= 1.6.0 we must not initialize gcrypt threading
+ *    because gcrypt will do that itself.
+ *
+ * So we need to init gcrypt threading if
+ *
+ *   - gcrypt < 1.6.0
+ * AND
+ *   - gnutls < 2.12
+ *
+ */
+
+#if (defined(CONFIG_GNUTLS_GCRYPT) &&           \
+     (!defined(GNUTLS_VERSION_NUMBER) ||        \
+      (GNUTLS_VERSION_NUMBER < 0x020c00)) &&    \
+     (!defined(GCRYPT_VERSION_NUMBER) ||        \
+      (GCRYPT_VERSION_NUMBER < 0x010600)))
+#define QCRYPTO_INIT_GCRYPT_THREADS
+#else
+#undef QCRYPTO_INIT_GCRYPT_THREADS
+#endif
+
 #ifdef DEBUG_GNUTLS
 static void qcrypto_gnutls_log(int level, const char *str)
 {
@@ -35,6 +70,49 @@ static void qcrypto_gnutls_log(int level, const char *str)
 }
 #endif
 
+#ifdef QCRYPTO_INIT_GCRYPT_THREADS
+static int qcrypto_gcrypt_mutex_init(void **priv)
+{                                                                             \
+    QemuMutex *lock = NULL;
+    lock = g_new0(QemuMutex, 1);
+    qemu_mutex_init(lock);
+    *priv = lock;
+    return 0;
+}
+
+static int qcrypto_gcrypt_mutex_destroy(void **priv)
+{
+    QemuMutex *lock = *priv;
+    qemu_mutex_destroy(lock);
+    g_free(lock);
+    return 0;
+}
+
+static int qcrypto_gcrypt_mutex_lock(void **priv)
+{
+    QemuMutex *lock = *priv;
+    qemu_mutex_lock(lock);
+    return 0;
+}
+
+static int qcrypto_gcrypt_mutex_unlock(void **priv)
+{
+    QemuMutex *lock = *priv;
+    qemu_mutex_unlock(lock);
+    return 0;
+}
+
+static struct gcry_thread_cbs qcrypto_gcrypt_thread_impl = {
+    (GCRY_THREAD_OPTION_PTHREAD | (GCRY_THREAD_OPTION_VERSION << 8)),
+    NULL,
+    qcrypto_gcrypt_mutex_init,
+    qcrypto_gcrypt_mutex_destroy,
+    qcrypto_gcrypt_mutex_lock,
+    qcrypto_gcrypt_mutex_unlock,
+    NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+};
+#endif /* QCRYPTO_INIT_GCRYPT */
+
 int qcrypto_init(Error **errp)
 {
     int ret;
@@ -49,6 +127,18 @@ int qcrypto_init(Error **errp)
     gnutls_global_set_log_level(10);
     gnutls_global_set_log_function(qcrypto_gnutls_log);
 #endif
+
+#ifdef CONFIG_GNUTLS_GCRYPT
+    if (!gcry_check_version(GCRYPT_VERSION)) {
+        error_setg(errp, "%s", _("Unable to initialize gcrypt"));
+        return -1;
+    }
+#ifdef QCRYPTO_INIT_GCRYPT_THREADS
+    gcry_control(GCRYCTL_SET_THREAD_CBS, &qcrypto_gcrypt_thread_impl);
+#endif /* QCRYPTO_INIT_GCRYPT_THREADS */
+    gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
+#endif
+
     return 0;
 }
 
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 13/34] crypto: add a nettle cipher implementation
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (11 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 12/34] crypto: add a gcrypt cipher implementation Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 14/34] crypto: introduce new module for handling TLS credentials Daniel P. Berrange
                   ` (21 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

If we are linking to gnutls already and gnutls is built against
nettle, then we should use nettle as a cipher backend in
preference to our built-in backend.

This will be used when linking against some GNUTLS 2.x versions
and all GNUTLS 3.x versions.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 configure              |  35 +++++++-
 crypto/cipher-nettle.c | 226 +++++++++++++++++++++++++++++++++++++++++++++++++
 crypto/cipher.c        |   4 +
 3 files changed, 262 insertions(+), 3 deletions(-)
 create mode 100644 crypto/cipher-nettle.c

diff --git a/configure b/configure
index 444e165..56ccbc9 100755
--- a/configure
+++ b/configure
@@ -2145,6 +2145,7 @@ fi
 # GNUTLS probe
 
 gnutls_gcrypt=no
+gnutls_nettle=no
 if test "$gnutls" != "no"; then
     if $pkg_config --exists "gnutls"; then
         gnutls_cflags=`$pkg_config --cflags gnutls`
@@ -2163,14 +2164,25 @@ if test "$gnutls" != "no"; then
 
 	if $pkg_config --exists 'gnutls >= 3.0'; then
 	    gnutls_gcrypt=no
+	    gnutls_nettle=yes
 	elif $pkg_config --exists 'gnutls >= 2.12'; then
 	    case `$pkg_config --libs --static gnutls` in
-		*gcrypt*) gnutls_gcrypt=yes     ;;
-		*nettle*) gnutls_gcrypt=no      ;;
-		*)        gnutls_gcrypt=yes     ;;
+		*gcrypt*)
+		    gnutls_gcrypt=yes
+		    gnutls_nettle=no
+		    ;;
+		*nettle*)
+		    gnutls_gcrypt=no
+		    gnutls_nettle=yes
+		    ;;
+		*)
+		    gnutls_gcrypt=yes
+		    gnutls_nettle=no
+		    ;;
 	    esac
 	else
 	    gnutls_gcrypt=yes
+	    gnutls_nettle=no
 	fi
     elif test "$gnutls" = "yes"; then
 	feature_not_found "gnutls" "Install gnutls devel"
@@ -2195,6 +2207,19 @@ if test "$gnutls_gcrypt" != "no"; then
 fi
 
 
+if test "$gnutls_nettle" != "no"; then
+    if $pkg_config --exists "nettle"; then
+        nettle_cflags=`$pkg_config --cflags nettle`
+        nettle_libs=`$pkg_config --libs nettle`
+        libs_softmmu="$nettle_libs $libs_softmmu"
+        libs_tools="$nettle_libs $libs_tools"
+	QEMU_CFLAGS="$QEMU_CFLAGS $nettle_cflags"
+    else
+	feature_not_found "nettle" "Install nettle devel"
+    fi
+fi
+
+
 ##########################################
 # VTE probe
 
@@ -4425,6 +4450,7 @@ echo "GTK support       $gtk"
 echo "GNUTLS support    $gnutls"
 echo "GNUTLS hash       $gnutls_hash"
 echo "GNUTLS gcrypt     $gnutls_gcrypt"
+echo "GNUTLS nettle     $gnutls_nettle"
 echo "VTE support       $vte"
 echo "curses support    $curses"
 echo "curl support      $curl"
@@ -4785,6 +4811,9 @@ fi
 if test "$gnutls_gcrypt" = "yes" ; then
   echo "CONFIG_GNUTLS_GCRYPT=y" >> $config_host_mak
 fi
+if test "$gnutls_nettle" = "yes" ; then
+  echo "CONFIG_GNUTLS_NETTLE=y" >> $config_host_mak
+fi
 if test "$vte" = "yes" ; then
   echo "CONFIG_VTE=y" >> $config_host_mak
   echo "VTE_CFLAGS=$vte_cflags" >> $config_host_mak
diff --git a/crypto/cipher-nettle.c b/crypto/cipher-nettle.c
new file mode 100644
index 0000000..a455319
--- /dev/null
+++ b/crypto/cipher-nettle.c
@@ -0,0 +1,226 @@
+/*
+ * QEMU Crypto cipher nettle algorithms
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/cipher.h"
+
+#include <glib/gi18n.h>
+#include <nettle/nettle-types.h>
+#include <nettle/aes.h>
+#include <nettle/des.h>
+#include <nettle/cbc.h>
+
+typedef struct QCryptoCipherNettle QCryptoCipherNettle;
+struct QCryptoCipherNettle {
+    void *ctx_encrypt;
+    void *ctx_decrypt;
+    nettle_crypt_func *alg_encrypt;
+    nettle_crypt_func *alg_decrypt;
+    uint8_t *iv;
+    size_t niv;
+};
+
+static uint8_t *qcrypto_cipher_munge_des_rfb_key(const uint8_t *key,
+                                                 size_t nkey)
+{
+    uint8_t *ret = g_new0(uint8_t, nkey);
+    size_t i;
+    for (i = 0; i < nkey; i++) {
+        uint8_t r = key[i];
+        r = (r & 0xf0)>>4 | (r & 0x0f)<<4;
+        r = (r & 0xcc)>>2 | (r & 0x33)<<2;
+        r = (r & 0xaa)>>1 | (r & 0x55)<<1;
+        ret[i] = r;
+    }
+    return ret;
+}
+
+
+bool qcrypto_cipher_supports(QCryptoCipherAlgorithm alg)
+{
+    if (alg == QCRYPTO_CIPHER_ALG_DES_RFB ||
+        alg == QCRYPTO_CIPHER_ALG_AES) {
+        return true;
+    }
+    return false;
+}
+
+
+QCryptoCipher *qcrypto_cipher_new(QCryptoCipherAlgorithm alg,
+                                  QCryptoCipherMode mode,
+                                  const uint8_t *key, size_t nkey,
+                                  Error **errp)
+{
+    QCryptoCipher *cipher;
+    QCryptoCipherNettle *ctx;
+    uint8_t *rfbkey;
+
+    switch (mode) {
+    case QCRYPTO_CIPHER_MODE_ECB:
+    case QCRYPTO_CIPHER_MODE_CBC:
+        break;
+    default:
+        error_setg(errp, _("Unsupported cipher mode %d"), mode);
+        return NULL;
+    }
+
+    cipher = g_new0(QCryptoCipher, 1);
+    cipher->alg = alg;
+    cipher->mode = mode;
+
+    ctx = g_new0(QCryptoCipherNettle, 1);
+
+    switch (alg) {
+    case QCRYPTO_CIPHER_ALG_DES_RFB:
+        if (nkey != DES_KEY_SIZE) {
+            error_setg(errp, _("Expected key size %d got %zu"),
+                       DES_KEY_SIZE, nkey);
+            goto error;
+        }
+        ctx->ctx_encrypt = g_new0(struct des_ctx, 1);
+        ctx->ctx_decrypt = NULL; /* 1 ctx can do both */
+        rfbkey = qcrypto_cipher_munge_des_rfb_key(key, nkey);
+        des_set_key(ctx->ctx_encrypt, rfbkey);
+        g_free(rfbkey);
+
+        ctx->alg_encrypt = (nettle_crypt_func *)des_encrypt;
+        ctx->alg_decrypt = (nettle_crypt_func *)des_decrypt;
+
+        ctx->niv = DES_BLOCK_SIZE;
+        break;
+
+    case QCRYPTO_CIPHER_ALG_AES:
+        if (nkey < AES_MIN_KEY_SIZE ||
+            nkey > AES_MAX_KEY_SIZE) {
+            error_setg(errp, _("Expected key size %d-%d got %zu"),
+                       AES_MIN_KEY_SIZE, AES_MAX_KEY_SIZE, nkey);
+            goto error;
+        }
+        ctx->ctx_encrypt = g_new0(struct aes_ctx, 1);
+        ctx->ctx_decrypt = g_new0(struct aes_ctx, 1);
+
+        aes_set_encrypt_key(ctx->ctx_encrypt, nkey, key);
+        aes_set_decrypt_key(ctx->ctx_decrypt, nkey, key);
+
+        ctx->alg_encrypt = (nettle_crypt_func *)aes_encrypt;
+        ctx->alg_decrypt = (nettle_crypt_func *)aes_decrypt;
+
+        ctx->niv = AES_BLOCK_SIZE;
+        break;
+    default:
+        error_setg(errp, _("Unsupported cipher algorithm %d"), alg);
+        goto error;
+    }
+
+    ctx->iv = g_new0(uint8_t, ctx->niv);
+    cipher->opaque = ctx;
+
+    return cipher;
+
+ error:
+    g_free(cipher);
+    g_free(ctx);
+    return NULL;
+}
+
+
+void qcrypto_cipher_free(QCryptoCipher *cipher)
+{
+    QCryptoCipherNettle *ctx;
+
+    if (!cipher) {
+        return;
+    }
+
+    ctx = cipher->opaque;
+    g_free(ctx->iv);
+    g_free(ctx->ctx_encrypt);
+    g_free(ctx->ctx_decrypt);
+    g_free(ctx);
+    g_free(cipher);
+}
+
+
+int qcrypto_cipher_encrypt(QCryptoCipher *cipher,
+                           const void *in,
+                           void *out,
+                           size_t len,
+                           Error **errp)
+{
+    QCryptoCipherNettle *ctx = cipher->opaque;
+
+    switch (cipher->mode) {
+    case QCRYPTO_CIPHER_MODE_ECB:
+        ctx->alg_encrypt(ctx->ctx_encrypt, len, out, in);
+        break;
+
+    case QCRYPTO_CIPHER_MODE_CBC:
+        cbc_encrypt(ctx->ctx_encrypt, ctx->alg_encrypt,
+                    ctx->niv, ctx->iv,
+                    len, out, in);
+        break;
+    default:
+        error_setg(errp, _("Unsupported cipher algorithm %d"),
+                   cipher->alg);
+        return -1;
+    }
+    return 0;
+}
+
+
+int qcrypto_cipher_decrypt(QCryptoCipher *cipher,
+                           const void *in,
+                           void *out,
+                           size_t len,
+                           Error **errp)
+{
+    QCryptoCipherNettle *ctx = cipher->opaque;
+
+    switch (cipher->mode) {
+    case QCRYPTO_CIPHER_MODE_ECB:
+        ctx->alg_decrypt(ctx->ctx_decrypt ? ctx->ctx_decrypt : ctx->ctx_encrypt,
+                         len, out, in);
+        break;
+
+    case QCRYPTO_CIPHER_MODE_CBC:
+        cbc_decrypt(ctx->ctx_decrypt ? ctx->ctx_decrypt : ctx->ctx_encrypt,
+                    ctx->alg_decrypt, ctx->niv, ctx->iv,
+                    len, out, in);
+        break;
+    default:
+        error_setg(errp, _("Unsupported cipher algorithm %d"),
+                   cipher->alg);
+        return -1;
+    }
+    return 0;
+}
+
+int qcrypto_cipher_setiv(QCryptoCipher *cipher,
+                         const uint8_t *iv, size_t niv,
+                         Error **errp)
+{
+    QCryptoCipherNettle *ctx = cipher->opaque;
+    if (niv != ctx->niv) {
+        error_setg(errp, _("Expected IV size %zu not %zu"),
+                   ctx->niv, niv);
+        return -1;
+    }
+    memcpy(ctx->iv, iv, niv);
+    return 0;
+}
diff --git a/crypto/cipher.c b/crypto/cipher.c
index dc140fb..3299a7a 100644
--- a/crypto/cipher.c
+++ b/crypto/cipher.c
@@ -23,5 +23,9 @@
 #ifdef CONFIG_GNUTLS_GCRYPT
 #include "crypto/cipher-gcrypt.c"
 #else
+#ifdef CONFIG_GNUTLS_NETTLE
+#include "crypto/cipher-nettle.c"
+#else
 #include "crypto/cipher-builtin.c"
 #endif
+#endif
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 14/34] crypto: introduce new module for handling TLS credentials
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (12 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 13/34] crypto: add a nettle " Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 15/34] crypto: add sanity checking of " Daniel P. Berrange
                   ` (20 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

Introduce a QCryptoTLSCreds class to store TLS credentials, for use
by later TLS session code. The class is setup as a user creatable object,
so instance can be created/deleted via 'object-add' and 'object-del'
QMP commands, or via the -object command line arg.

If the credentials cannot be initialized an error will be reported
as a QMP reply, or on stderr respectively.

A later patch will update the VNC server to use this eg

   qemu-system-x86_64 -object qcrypto-tls-creds,id=tls0,... \
         -vnc 127.0.0.1:1,tls-creds=tls0

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 crypto/Makefile.objs      |   1 +
 crypto/init.c             |   8 +
 crypto/tlscreds.c         | 566 ++++++++++++++++++++++++++++++++++++++++++++++
 include/crypto/tlscreds.h | 134 +++++++++++
 qemu-options.hx           |  30 +++
 tests/Makefile            |   4 +-
 6 files changed, 741 insertions(+), 2 deletions(-)
 create mode 100644 crypto/tlscreds.c
 create mode 100644 include/crypto/tlscreds.h

diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs
index b050138..cf62d51 100644
--- a/crypto/Makefile.objs
+++ b/crypto/Makefile.objs
@@ -3,3 +3,4 @@ util-obj-y += hash.o
 util-obj-y += aes.o
 util-obj-y += desrfb.o
 util-obj-y += cipher.o
+util-obj-y += tlscreds.o
diff --git a/crypto/init.c b/crypto/init.c
index 486af37..c2d04bf 100644
--- a/crypto/init.c
+++ b/crypto/init.c
@@ -19,6 +19,7 @@
  */
 
 #include "crypto/init.h"
+#include "crypto/tlscreds.h"
 #include "qemu/thread.h"
 
 #include <glib/gi18n.h>
@@ -139,6 +140,13 @@ int qcrypto_init(Error **errp)
     gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
 #endif
 
+    /* XXX hack - if we don't reference any function in tlscreds.c
+     * then the linker drops tlscred.o from libqemutil.a when it
+     * links the emulators as it thinks it is unused. It isn't
+     * clever enough to see the constructor :-(
+     */
+    qcrypto_tls_creds_dummy();
+
     return 0;
 }
 
diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c
new file mode 100644
index 0000000..9baf547
--- /dev/null
+++ b/crypto/tlscreds.c
@@ -0,0 +1,566 @@
+/*
+ * QEMU crypto TLS credential support
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/tlscreds.h"
+#include "qom/object_interfaces.h"
+
+#include <glib/gi18n.h>
+
+/* #define QCRYPTO_DEBUG */
+
+#ifdef QCRYPTO_DEBUG
+#define DPRINTF(fmt, ...) do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do { } while (0)
+#endif
+
+
+#define DH_BITS 2048
+
+static const char * const endpoint_map[QCRYPTO_TLS_CREDS_ENDPOINT_LAST + 1] = {
+    [QCRYPTO_TLS_CREDS_ENDPOINT_SERVER] = "server",
+    [QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT] = "client",
+    [QCRYPTO_TLS_CREDS_ENDPOINT_LAST] = NULL,
+};
+
+static const char * const type_map[QCRYPTO_TLS_CREDS_TYPE_LAST + 1] = {
+    [QCRYPTO_TLS_CREDS_TYPE_NONE] = "none",
+    [QCRYPTO_TLS_CREDS_TYPE_ANON] = "anon",
+    [QCRYPTO_TLS_CREDS_TYPE_X509] = "x509",
+    [QCRYPTO_TLS_CREDS_TYPE_LAST] = NULL,
+};
+
+#ifdef CONFIG_GNUTLS
+static int qcrypto_tls_creds_set_dh_params_file(QCryptoTLSCreds *creds,
+                                                const char *filename,
+                                                Error **errp)
+{
+    int ret;
+
+    DPRINTF("Loading DH params %s\n", filename ? filename : "<generated>");
+    if (filename == NULL) {
+        ret = gnutls_dh_params_init(&creds->dh_params);
+        if (ret < 0) {
+            error_setg(errp, _("Unable to initialize DH parameters %s"),
+                       gnutls_strerror(ret));
+            return -1;
+        }
+        ret = gnutls_dh_params_generate2(creds->dh_params, DH_BITS);
+        if (ret < 0) {
+            gnutls_dh_params_deinit(creds->dh_params);
+            creds->dh_params = NULL;
+            error_setg(errp, _("Unable to generate DH parameters %s"),
+                       gnutls_strerror(ret));
+            return -1;
+        }
+    } else {
+        GError *gerr = NULL;
+        gchar *contents;
+        gsize len;
+        gnutls_datum_t data;
+        if (!g_file_get_contents(filename,
+                                 &contents,
+                                 &len,
+                                 &gerr)) {
+            error_setg(errp, "%s", gerr->message);
+            g_error_free(gerr);
+            return -1;
+        }
+        data.data = (unsigned char *)contents;
+        data.size = len;
+        ret = gnutls_dh_params_init(&creds->dh_params);
+        if (ret < 0) {
+            g_free(contents);
+            error_setg(errp, _("Unable to initialize DH parameters %s"),
+                       gnutls_strerror(ret));
+            return -1;
+        }
+        ret = gnutls_dh_params_import_pkcs3(creds->dh_params,
+                                            &data,
+                                            GNUTLS_X509_FMT_PEM);
+        g_free(contents);
+        if (ret < 0) {
+            gnutls_dh_params_deinit(creds->dh_params);
+            creds->dh_params = NULL;
+            error_setg(errp, _("Unable to load DH parameters from %s: %s"),
+                       filename, gnutls_strerror(ret));
+            return -1;
+        }
+    }
+
+    switch (creds->type) {
+    case QCRYPTO_TLS_CREDS_TYPE_NONE:
+        break;
+    case QCRYPTO_TLS_CREDS_TYPE_ANON:
+        gnutls_anon_set_server_dh_params(creds->data.anonServer,
+                                         creds->dh_params);
+        break;
+    case QCRYPTO_TLS_CREDS_TYPE_X509:
+        gnutls_certificate_set_dh_params(creds->data.x509,
+                                         creds->dh_params);
+        break;
+    case QCRYPTO_TLS_CREDS_TYPE_LAST:
+    default:
+        break;
+    }
+
+    return 0;
+}
+
+
+static int qcrypto_tls_creds_get_path(QCryptoTLSCreds *creds,
+                                      const char *filename,
+                                      bool required,
+                                      char **cred,
+                                      Error **errp)
+{
+    struct stat sb;
+    int ret = -1;
+
+    if (!creds->dir) {
+        error_setg(errp, "%s",
+                   _("Missing 'dir' property value"));
+        return -1;
+    }
+
+    *cred = g_strdup_printf("%s/%s", creds->dir, filename);
+
+    if (stat(*cred, &sb) < 0) {
+        if (errno == ENOENT && !required) {
+            ret = 0;
+        } else {
+            error_setg_errno(errp, errno,
+                             _("Unable to access credentials %s"),
+                             *cred);
+        }
+        g_free(*cred);
+        *cred = NULL;
+        goto cleanup;
+    }
+
+    DPRINTF("Resolved file %s\n", *cred ? *cred : "<none>");
+    ret = 0;
+ cleanup:
+    return ret;
+}
+
+
+
+static int
+qcrypto_tls_creds_load_x509(QCryptoTLSCreds *creds,
+                            Error **errp)
+{
+    char *cacert = NULL, *cacrl = NULL, *cert = NULL,
+        *key = NULL, *dhparams = NULL;
+    int ret;
+    int rv = -1;
+
+    if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
+        if (qcrypto_tls_creds_get_path(creds,
+                                       QCRYPTO_TLS_CREDS_X509_CA_CERT,
+                                       true, &cacert, errp) < 0 ||
+            qcrypto_tls_creds_get_path(creds,
+                                       QCRYPTO_TLS_CREDS_X509_CA_CRL,
+                                       false, &cacrl, errp) < 0 ||
+            qcrypto_tls_creds_get_path(creds,
+                                       QCRYPTO_TLS_CREDS_X509_SERVER_CERT,
+                                       true, &cert, errp) < 0 ||
+            qcrypto_tls_creds_get_path(creds,
+                                       QCRYPTO_TLS_CREDS_X509_SERVER_KEY,
+                                       true, &key, errp) < 0 ||
+            qcrypto_tls_creds_get_path(creds,
+                                       QCRYPTO_TLS_CREDS_DH_PARAMS,
+                                       false, &dhparams, errp) < 0) {
+            goto cleanup;
+        }
+    } else {
+        if (qcrypto_tls_creds_get_path(creds,
+                                       QCRYPTO_TLS_CREDS_X509_CA_CERT,
+                                       true, &cacert, errp) < 0 ||
+            qcrypto_tls_creds_get_path(creds,
+                                       QCRYPTO_TLS_CREDS_X509_CLIENT_CERT,
+                                       false, &cert, errp) < 0 ||
+            qcrypto_tls_creds_get_path(creds,
+                                       QCRYPTO_TLS_CREDS_X509_CLIENT_KEY,
+                                       false, &key, errp) < 0) {
+            goto cleanup;
+        }
+    }
+
+    ret = gnutls_certificate_allocate_credentials(&creds->data.x509);
+    if (ret < 0) {
+        error_setg(errp, _("Cannot allocate credentials '%s'"),
+                   gnutls_strerror(ret));
+        goto cleanup;
+    }
+
+    ret = gnutls_certificate_set_x509_trust_file(creds->data.x509,
+                                                 cacert,
+                                                 GNUTLS_X509_FMT_PEM);
+    if (ret < 0) {
+        error_setg(errp, _("Cannot load CA certificate '%s': %s"),
+                   cacert, gnutls_strerror(ret));
+        goto cleanup;
+    }
+
+    ret = gnutls_certificate_set_x509_key_file(creds->data.x509,
+                                               cert, key,
+                                               GNUTLS_X509_FMT_PEM);
+    if (ret < 0) {
+        error_setg(errp, _("Cannot load certificate '%s' & key '%s': %s"),
+                   cert, key, gnutls_strerror(ret));
+        goto cleanup;
+    }
+
+    if (cacrl) {
+        ret = gnutls_certificate_set_x509_crl_file(creds->data.x509,
+                                                   cacrl,
+                                                   GNUTLS_X509_FMT_PEM);
+        if (ret < 0) {
+            error_setg(errp, _("Cannot load CRL '%s': %s"),
+                       cacrl, gnutls_strerror(ret));
+            goto cleanup;
+        }
+    }
+
+    if ((creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) &&
+        qcrypto_tls_creds_set_dh_params_file(creds, dhparams, errp) < 0) {
+        goto cleanup;
+    }
+
+    rv = 0;
+ cleanup:
+    g_free(cacert);
+    g_free(cacrl);
+    g_free(cert);
+    g_free(key);
+    g_free(dhparams);
+    return rv;
+}
+
+
+static int
+qcrypto_tls_creds_load_anon(QCryptoTLSCreds *creds,
+                            Error **errp)
+{
+    char *dhparams = NULL;
+    int ret;
+    int rv = -1;
+
+    if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
+        if (qcrypto_tls_creds_get_path(creds,
+                                       QCRYPTO_TLS_CREDS_DH_PARAMS,
+                                       false, &dhparams, errp) < 0) {
+            goto cleanup;
+        }
+
+        ret = gnutls_anon_allocate_server_credentials(&creds->data.anonServer);
+        if (ret < 0) {
+            error_setg(errp, _("Cannot allocate credentials: %s"),
+                       gnutls_strerror(ret));
+            goto cleanup;
+        }
+
+        if (qcrypto_tls_creds_set_dh_params_file(creds, dhparams, errp) < 0) {
+            goto cleanup;
+        }
+    } else {
+        ret = gnutls_anon_allocate_client_credentials(&creds->data.anonClient);
+        if (ret < 0) {
+            error_setg(errp, _("Cannot allocate credentials: %s"),
+                       gnutls_strerror(ret));
+            goto cleanup;
+        }
+    }
+
+    rv = 0;
+ cleanup:
+    g_free(dhparams);
+    return rv;
+}
+
+static void qcrypto_tls_creds_load(QCryptoTLSCreds *creds,
+                                   Error **errp)
+{
+    DPRINTF("Loading creds %d from %s\n",
+            creds->type, creds->dir ? creds->dir : "<nodir>");
+    switch (creds->type) {
+    case QCRYPTO_TLS_CREDS_TYPE_NONE:
+        error_setg(errp, "%s", _("Missing 'credtype' property value"));
+        break;
+    case QCRYPTO_TLS_CREDS_TYPE_ANON:
+        if (qcrypto_tls_creds_load_anon(creds,
+                                        errp) < 0) {
+            return;
+        }
+        break;
+    case QCRYPTO_TLS_CREDS_TYPE_X509:
+        if (qcrypto_tls_creds_load_x509(creds,
+                                        errp) < 0) {
+            return;
+        }
+        break;
+    case QCRYPTO_TLS_CREDS_TYPE_LAST:
+    default:
+        error_setg(errp, _("Unknown TLS credential type %d"), creds->type);
+        break;
+    }
+}
+
+#else
+static void qcrypto_tls_creds_load(QCryptoTLSCreds *creds G_GNUC_UNUSED,
+                                   Error **errp)
+{
+    error_setg(errp, "%s", _("TLS credentials support requires GNUTLS"));
+}
+#endif /* ! CONFIG_GNUTLS */
+
+#ifdef CONFIG_GNUTLS
+static void qcrypto_tls_creds_unload(QCryptoTLSCreds *creds)
+{
+    switch (creds->type) {
+    case QCRYPTO_TLS_CREDS_TYPE_NONE:
+        break;
+    case QCRYPTO_TLS_CREDS_TYPE_ANON:
+        if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) {
+            if (creds->data.anonClient) {
+                gnutls_anon_free_client_credentials(creds->data.anonClient);
+                creds->data.anonClient = NULL;
+            }
+        } else {
+            if (creds->data.anonServer) {
+                gnutls_anon_free_server_credentials(creds->data.anonServer);
+                creds->data.anonServer = NULL;
+            }
+        }
+        break;
+    case QCRYPTO_TLS_CREDS_TYPE_X509:
+        if (creds->data.x509) {
+            gnutls_certificate_free_credentials(creds->data.x509);
+            creds->data.x509 = NULL;
+        }
+        break;
+    case QCRYPTO_TLS_CREDS_TYPE_LAST:
+    default:
+        break;
+    };
+    if (creds->dh_params) {
+        gnutls_dh_params_deinit(creds->dh_params);
+        creds->dh_params = NULL;
+    }
+}
+#else /* ! CONFIG_GNUTLS */
+static void qcrypto_tls_creds_unload(QCryptoTLSCreds *creds G_GNUC_UNUSED)
+{
+    /* nada */
+}
+#endif /* ! CONFIG_GNUTLS */
+
+static void qcrypto_tls_creds_prop_set_loaded(Object *obj,
+                                              bool value,
+                                              Error **errp)
+{
+    QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj);
+
+    if (value) {
+        qcrypto_tls_creds_load(creds, errp);
+    } else {
+        qcrypto_tls_creds_unload(creds);
+    }
+}
+
+
+#ifdef CONFIG_GNUTLS
+static bool qcrypto_tls_creds_prop_get_loaded(Object *obj,
+                                              Error **errp G_GNUC_UNUSED)
+{
+    QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj);
+
+    switch (creds->type) {
+    case QCRYPTO_TLS_CREDS_TYPE_NONE:
+        return false;
+
+    case QCRYPTO_TLS_CREDS_TYPE_X509:
+        return creds->data.x509 != NULL;
+
+    case QCRYPTO_TLS_CREDS_TYPE_ANON:
+        if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
+            return creds->data.anonServer != NULL;
+        } else {
+            return creds->data.anonClient != NULL;
+        }
+    case QCRYPTO_TLS_CREDS_TYPE_LAST:
+    default:
+        return false;
+    }
+}
+
+#else /* ! CONFIG_GNUTLS */
+static bool qcrypto_tls_creds_prop_get_loaded(Object *obj G_GNUC_UNUSED,
+                                              Error **errp G_GNUC_UNUSED)
+{
+    return false;
+}
+#endif /* ! CONFIG_GNUTLS */
+
+static void qcrypto_tls_creds_prop_set_verify(Object *obj,
+                                              bool value,
+                                              Error **errp G_GNUC_UNUSED)
+{
+    QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj);
+
+    creds->verifyPeer = value;
+}
+
+
+static bool qcrypto_tls_creds_prop_get_verify(Object *obj,
+                                              Error **errp G_GNUC_UNUSED)
+{
+    QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj);
+
+    return creds->verifyPeer;
+}
+
+static void qcrypto_tls_creds_prop_set_dir(Object *obj,
+                                           const char *value,
+                                           Error **errp G_GNUC_UNUSED)
+{
+    QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj);
+
+    creds->dir = g_strdup(value);
+}
+
+static char *qcrypto_tls_creds_prop_get_dir(Object *obj,
+                                            Error **errp G_GNUC_UNUSED)
+{
+    QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj);
+
+    return g_strdup(creds->dir);
+}
+
+static void qcrypto_tls_creds_prop_set_type(Object *obj,
+                                            int value,
+                                            Error **errp G_GNUC_UNUSED)
+{
+    QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj);
+
+    creds->type = value;
+}
+
+static int qcrypto_tls_creds_prop_get_type(Object *obj,
+                                           Error **errp G_GNUC_UNUSED)
+{
+    QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj);
+
+    return creds->type;
+}
+
+static void qcrypto_tls_creds_prop_set_endpoint(Object *obj,
+                                                int value,
+                                                Error **errp G_GNUC_UNUSED)
+{
+    QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj);
+
+    creds->endpoint = value;
+}
+
+static int qcrypto_tls_creds_prop_get_endpoint(Object *obj,
+                                               Error **errp G_GNUC_UNUSED)
+{
+    QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj);
+
+    return creds->endpoint;
+}
+
+
+static void qcrypto_tls_creds_complete(UserCreatable *uc, Error **errp)
+{
+    object_property_set_bool(OBJECT(uc), true, "loaded", errp);
+}
+
+
+static void qcrypto_tls_creds_init(Object *obj G_GNUC_UNUSED)
+{
+    object_property_add_bool(obj, "loaded",
+                             qcrypto_tls_creds_prop_get_loaded,
+                             qcrypto_tls_creds_prop_set_loaded,
+                             NULL);
+    object_property_add_bool(obj, "verify-peer",
+                             qcrypto_tls_creds_prop_get_verify,
+                             qcrypto_tls_creds_prop_set_verify,
+                             NULL);
+    object_property_add_str(obj, "dir",
+                            qcrypto_tls_creds_prop_get_dir,
+                            qcrypto_tls_creds_prop_set_dir,
+                            NULL);
+    object_property_add_enum(obj, "credtype",
+                             type_map,
+                             qcrypto_tls_creds_prop_get_type,
+                             qcrypto_tls_creds_prop_set_type,
+                             NULL);
+    object_property_add_enum(obj, "endpoint",
+                             endpoint_map,
+                             qcrypto_tls_creds_prop_get_endpoint,
+                             qcrypto_tls_creds_prop_set_endpoint,
+                             NULL);
+}
+
+
+static void qcrypto_tls_creds_finalize(Object *obj G_GNUC_UNUSED)
+{
+    QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj);
+
+    qcrypto_tls_creds_unload(creds);
+}
+
+
+static void qcrypto_tls_creds_class_init(ObjectClass *oc, void *data)
+{
+    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
+
+    ucc->complete = qcrypto_tls_creds_complete;
+}
+
+
+static const TypeInfo qcrypto_tls_creds_info = {
+    .parent = TYPE_OBJECT,
+    .name = TYPE_QCRYPTO_TLS_CREDS,
+    .instance_size = sizeof(QCryptoTLSCreds),
+    .instance_init = qcrypto_tls_creds_init,
+    .instance_finalize = qcrypto_tls_creds_finalize,
+    .class_size = sizeof(QCryptoTLSCredsClass),
+    .class_init = qcrypto_tls_creds_class_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_USER_CREATABLE },
+        { }
+    }
+};
+
+static void qcrypto_tls_creds_register_types(void)
+{
+    DPRINTF("Register tls\n");
+    type_register_static(&qcrypto_tls_creds_info);
+}
+
+void qcrypto_tls_creds_dummy(void)
+{
+}
+
+type_init(qcrypto_tls_creds_register_types);
diff --git a/include/crypto/tlscreds.h b/include/crypto/tlscreds.h
new file mode 100644
index 0000000..c9d2009
--- /dev/null
+++ b/include/crypto/tlscreds.h
@@ -0,0 +1,134 @@
+/*
+ * QEMU crypto TLS credential support
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QCRYPTO_TLSCRED_H__
+#define QCRYPTO_TLSCRED_H__
+
+#include "qemu-common.h"
+#include "qapi/error.h"
+#include "qom/object.h"
+
+#ifdef CONFIG_GNUTLS
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#endif
+
+#define TYPE_QCRYPTO_TLS_CREDS "qcrypto-tls-creds"
+#define QCRYPTO_TLS_CREDS(obj)                  \
+    OBJECT_CHECK(QCryptoTLSCreds, (obj), TYPE_QCRYPTO_TLS_CREDS)
+
+typedef struct QCryptoTLSCreds QCryptoTLSCreds;
+typedef struct QCryptoTLSCredsClass QCryptoTLSCredsClass;
+
+
+#define QCRYPTO_TLS_CREDS_DH_PARAMS "dh-params.pem"
+#define QCRYPTO_TLS_CREDS_X509_CA_CERT "ca-cert.pem"
+#define QCRYPTO_TLS_CREDS_X509_CA_CRL "ca-crl.pem"
+#define QCRYPTO_TLS_CREDS_X509_SERVER_KEY "server-key.pem"
+#define QCRYPTO_TLS_CREDS_X509_SERVER_CERT "server-cert.pem"
+#define QCRYPTO_TLS_CREDS_X509_CLIENT_KEY "client-key.pem"
+#define QCRYPTO_TLS_CREDS_X509_CLIENT_CERT "client-cert.pem"
+
+typedef enum {
+    QCRYPTO_TLS_CREDS_TYPE_NONE,
+    QCRYPTO_TLS_CREDS_TYPE_ANON,
+    QCRYPTO_TLS_CREDS_TYPE_X509,
+
+    QCRYPTO_TLS_CREDS_TYPE_LAST,
+} QCryptoTLSCredsType;
+
+typedef enum {
+    QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
+    QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT,
+
+    QCRYPTO_TLS_CREDS_ENDPOINT_LAST,
+} QCryptoTLSCredsEndpoint;
+
+
+/**
+ * QCryptoTLSCreds:
+ *
+ * The QCryptoTLSCreds object holds a set of credentials used to
+ * perform a TLS handshake. Commonly x509 credentials would be
+ * used, but this also supports anonymous crdentials. The latter
+ * are generally discouraged due to lacking MITM attack protection
+ * amongst other things.
+ *
+ * This is a user creatable object, which can be instantiated
+ * via object_new_propv():
+ *
+ *   Object *obj;
+ *   Error *err = NULL;
+ *   obj = object_new_propv(TYPE_QCRYPTO_TLS_CREDS,
+ *                          "tlscreds0",
+ *                          &err,
+ *                          "credstype", "x509",
+ *                          "endpoint", "server",
+ *                          "dir", "/path/x509/cert/dir",
+ *                          "verify-peer", "yes",
+ *                          NULL);
+ *
+ * Or via QMP:
+ *
+ *    {
+ *       "execute": "object-add", "arguments": {
+ *          "id": "tlscreds0",
+ *          "qom-type": "qcrypto-tls-creds",
+ *          "props": {
+ *             "credtype": "x509",
+ *             "endpoint": "server",
+ *             "dir": "/path/to/x509/cert/dir",
+ *             "verify-peer": false
+ *          }
+ *       }
+ *    }
+ *
+ * Or via the CLI:
+ *
+ *  qemu-system-x86_64 -object qcrypto-tls-creds,id=tlscreds0,\
+ *          credtype=x509,endpoint=server,verify-peer=off,\
+ *          dir=/path/to/x509/certdir/
+ *
+ */
+
+struct QCryptoTLSCreds {
+    Object parent;
+    char *dir;
+    QCryptoTLSCredsType type;
+    QCryptoTLSCredsEndpoint endpoint;
+#ifdef CONFIG_GNUTLS
+    union {
+        gnutls_certificate_credentials_t x509;
+        gnutls_anon_server_credentials_t anonServer;
+        gnutls_anon_client_credentials_t anonClient;
+    } data;
+    gnutls_dh_params_t dh_params;
+#endif
+    bool verifyPeer;
+};
+
+struct QCryptoTLSCredsClass {
+    ObjectClass parent;
+};
+
+void qcrypto_tls_creds_dummy(void);
+
+#endif /* QCRYPTO_TLSCRED_H__ */
+
diff --git a/qemu-options.hx b/qemu-options.hx
index 5ef0ae4..1c88253 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -3495,6 +3495,36 @@ the @option{virtio-rng} device. The @option{chardev} parameter is
 the unique ID of a character device backend that provides the connection
 to the RNG daemon.
 
+@item -object qcrypto-tls-cred,id=@var{id},credtype=@var{type},endpoint=@var{endpoint},dir=@var{/path/to/cred/dir},verify-peer=@var{on|off}
+
+Creates a TLS credentials object, which can be used to provide TLS
+support on the VNC server and other network backends. The @option{id}
+parameter is a unique ID which network backends will use to access
+the credentials. The @option{credtype} parameter takes one of the
+values @var{anon}, for anonymous credentials or @var{x509} for
+x509 certificate credentials. The @option{endpoint} is either
+@option{server} or @option{client} depending on whether the QEMU
+network backend that uses the credentials will be acting as a client
+or as a server. If @option{verify-peer} is enabled (the default)
+then once the handshake is completed, the peer credentials will be
+verified. With x509 certificates, this implies that the clients
+must be provided with valid client certificates too.
+
+The @var{dir} parameter tells QEMU where to find the credential
+files. For server endpoints, this directory may contain a file
+@var{dh-params.pem} providing diffie-hellman parameters to use
+for the TLS server. If the file is missing, QEMU will generate
+a set of DH parameters at startup. This is a computationally
+expensive operation that consumes random pool entropy, so it is
+recommended that a persistent set of parameters be generated
+upfront and saved.
+
+For x509 certificate credentials the directory will contain further files
+providing the x509 certificates. The certificates must be stored
+in PEM format, in filenames @var{ca-cert.pem}, @var{ca-crl.pem} (optional),
+@var{server-cert.pem} (only servers), @var{server-key.pem} (only servers),
+@var{client-cert.pem} (only clients), and @var{client-key.pem} (only clients).
+
 @end table
 
 ETEXI
diff --git a/tests/Makefile b/tests/Makefile
index 252de54..e920dae 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -311,8 +311,8 @@ tests/test-opts-visitor$(EXESUF): tests/test-opts-visitor.o $(test-qapi-obj-y) l
 
 tests/test-mul64$(EXESUF): tests/test-mul64.o libqemuutil.a
 tests/test-bitops$(EXESUF): tests/test-bitops.o libqemuutil.a
-tests/test-crypto-hash$(EXESUF): tests/test-crypto-hash.o libqemuutil.a libqemustub.a
-tests/test-crypto-cipher$(EXESUF): tests/test-crypto-cipher.o libqemuutil.a libqemustub.a
+tests/test-crypto-hash$(EXESUF): tests/test-crypto-hash.o $(qom-core-obj) libqemuutil.a libqemustub.a
+tests/test-crypto-cipher$(EXESUF): tests/test-crypto-cipher.o $(qom-core-obj) libqemuutil.a libqemustub.a
 
 libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o
 libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 15/34] crypto: add sanity checking of TLS credentials
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (13 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 14/34] crypto: introduce new module for handling TLS credentials Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 16/34] crypto: introduce new module for handling TLS sessions Daniel P. Berrange
                   ` (19 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

If the administrator incorrectly sets up their x509 certificates,
the errors seen at runtime during connection attempts are very
obscure and difficult to diagnose. This adds support in the TLS
credentials object to sanity check the certificates when QEMU
first loads them. This gives the administrator immediate feedback
for the majority of common configuration mistakes.

The code is derived from equivalent code that has been part of
libvirt's TLS support for a while.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 configure                    |   22 +
 crypto/tlscreds.c            |  527 ++++++++++++++++++++
 include/crypto/tlscreds.h    |    1 +
 tests/.gitignore             |    3 +
 tests/Makefile               |    7 +-
 tests/crypto-tls-helpers.c   |  485 +++++++++++++++++++
 tests/crypto-tls-helpers.h   |  133 +++++
 tests/pkix_asn1_tab.c        | 1103 ++++++++++++++++++++++++++++++++++++++++++
 tests/test-crypto-tlscreds.c |  727 ++++++++++++++++++++++++++++
 9 files changed, 3007 insertions(+), 1 deletion(-)
 create mode 100644 tests/crypto-tls-helpers.c
 create mode 100644 tests/crypto-tls-helpers.h
 create mode 100644 tests/pkix_asn1_tab.c
 create mode 100644 tests/test-crypto-tlscreds.c

diff --git a/configure b/configure
index 56ccbc9..f7ec73d 100755
--- a/configure
+++ b/configure
@@ -416,6 +416,9 @@ if test "$debug_info" = "yes"; then
     LDFLAGS="-g $LDFLAGS"
 fi
 
+test_cflags=""
+test_libs=""
+
 # make source path absolute
 source_path=`cd "$source_path"; pwd`
 
@@ -2219,6 +2222,19 @@ if test "$gnutls_nettle" != "no"; then
     fi
 fi
 
+##########################################
+# libtasn1 - only for the TLS creds/session test suite
+
+tasn1=yes
+if $pkg_config --exists "libtasn1"; then
+    tasn1_cflags=`$pkg_config --cflags libtasn1`
+    tasn1_libs=`$pkg_config --libs libtasn1`
+    test_cflags="$test_cflags $tasn1_cflags"
+    test_libs="$test_libs $tasn1_libs"
+else
+    tasn1=no
+fi
+
 
 ##########################################
 # VTE probe
@@ -4451,6 +4467,7 @@ echo "GNUTLS support    $gnutls"
 echo "GNUTLS hash       $gnutls_hash"
 echo "GNUTLS gcrypt     $gnutls_gcrypt"
 echo "GNUTLS nettle     $gnutls_nettle"
+echo "libtasn1          $tasn1"
 echo "VTE support       $vte"
 echo "curses support    $curses"
 echo "curl support      $curl"
@@ -4814,6 +4831,9 @@ fi
 if test "$gnutls_nettle" = "yes" ; then
   echo "CONFIG_GNUTLS_NETTLE=y" >> $config_host_mak
 fi
+if test "$tasn1" = "yes" ; then
+  echo "CONFIG_TASN1=y" >> $config_host_mak
+fi
 if test "$vte" = "yes" ; then
   echo "CONFIG_VTE=y" >> $config_host_mak
   echo "VTE_CFLAGS=$vte_cflags" >> $config_host_mak
@@ -5141,6 +5161,8 @@ echo "EXESUF=$EXESUF" >> $config_host_mak
 echo "DSOSUF=$DSOSUF" >> $config_host_mak
 echo "LDFLAGS_SHARED=$LDFLAGS_SHARED" >> $config_host_mak
 echo "LIBS_QGA+=$libs_qga" >> $config_host_mak
+echo "TEST_LIBS=$test_libs" >> $config_host_mak
+echo "TEST_CFLAGS=$test_cflags" >> $config_host_mak
 echo "POD2MAN=$POD2MAN" >> $config_host_mak
 echo "TRANSLATE_OPT_CFLAGS=$TRANSLATE_OPT_CFLAGS" >> $config_host_mak
 if test "$gcov" = "yes" ; then
diff --git a/crypto/tlscreds.c b/crypto/tlscreds.c
index 9baf547..190a1b5 100644
--- a/crypto/tlscreds.c
+++ b/crypto/tlscreds.c
@@ -161,6 +161,504 @@ static int qcrypto_tls_creds_get_path(QCryptoTLSCreds *creds,
     return ret;
 }
 
+static int qcrypto_tls_creds_check_cert_times(gnutls_x509_crt_t cert,
+                                              const char *certFile,
+                                              bool isServer,
+                                              bool isCA,
+                                              Error **errp)
+{
+    time_t now = time(NULL);
+
+    if (now == ((time_t)-1)) {
+        error_setg_errno(errp, errno, "%s",
+                         _("cannot get current time"));
+        return -1;
+    }
+
+    if (gnutls_x509_crt_get_expiration_time(cert) < now) {
+        error_setg(errp,
+                   (isCA ?
+                    _("The CA certificate %s has expired") :
+                    (isServer ?
+                     _("The server certificate %s has expired") :
+                     _("The client certificate %s has expired"))),
+                   certFile);
+        return -1;
+    }
+
+    if (gnutls_x509_crt_get_activation_time(cert) > now) {
+        error_setg(errp,
+                   (isCA ?
+                    _("The CA certificate %s is not yet active") :
+                    (isServer ?
+                     _("The server certificate %s is not yet active") :
+                     _("The client certificate %s is not yet active"))),
+                   certFile);
+        return -1;
+    }
+
+    return 0;
+}
+
+
+#if LIBGNUTLS_VERSION_NUMBER >= 2
+/*
+ * The gnutls_x509_crt_get_basic_constraints function isn't
+ * available in GNUTLS 1.0.x branches. This isn't critical
+ * though, since gnutls_certificate_verify_peers2 will do
+ * pretty much the same check at runtime, so we can just
+ * disable this code
+ */
+static int
+qcrypto_tls_creds_check_cert_basic_constraints(gnutls_x509_crt_t cert,
+                                               const char *certFile,
+                                               bool isServer,
+                                               bool isCA,
+                                               Error **errp)
+{
+    int status;
+
+    status = gnutls_x509_crt_get_basic_constraints(cert, NULL, NULL, NULL);
+    DPRINTF("Cert %s basic constraints %d", certFile, status);
+
+    if (status > 0) { /* It is a CA cert */
+        if (!isCA) {
+            error_setg(errp, isServer ?
+                       _("The certificate %s basic constraints show a CA, "
+                         "but we need one for a server") :
+                       _("The certificate %s basic constraints show a CA, "
+                         "but we need one for a client"),
+                       certFile);
+            return -1;
+        }
+    } else if (status == 0) { /* It is not a CA cert */
+        if (isCA) {
+            error_setg(errp,
+                       _("The certificate %s basic constraints do not "
+                         "show a CA"),
+                       certFile);
+            return -1;
+        }
+    } else if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
+        /* Missing basicConstraints */
+        if (isCA) {
+            error_setg(errp,
+                       _("The certificate %s is missing basic constraints "
+                         "for a CA"),
+                       certFile);
+            return -1;
+        }
+    } else { /* General error */
+        error_setg(errp,
+                   _("Unable to query certificate %s basic constraints %s"),
+                   certFile, gnutls_strerror(status));
+        return -1;
+    }
+
+    return 0;
+}
+#endif
+
+
+static int qcrypto_tls_creds_check_cert_key_usage(gnutls_x509_crt_t cert,
+                                                  const char *certFile,
+                                                  bool isCA,
+                                                  Error **errp)
+{
+    int status;
+    unsigned int usage = 0;
+    unsigned int critical = 0;
+
+    status = gnutls_x509_crt_get_key_usage(cert, &usage, &critical);
+
+    DPRINTF("Cert %s key usage status %d usage %d critical %u",
+            certFile, status, usage, critical);
+    if (status < 0) {
+        if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
+            usage = isCA ? GNUTLS_KEY_KEY_CERT_SIGN :
+                GNUTLS_KEY_DIGITAL_SIGNATURE|GNUTLS_KEY_KEY_ENCIPHERMENT;
+        } else {
+            error_setg(errp,
+                       _("Unable to query certificate %s key usage %s"),
+                       certFile, gnutls_strerror(status));
+            return -1;
+        }
+    }
+
+    if (isCA) {
+        if (!(usage & GNUTLS_KEY_KEY_CERT_SIGN)) {
+            if (critical) {
+                error_setg(errp,
+                           _("Certificate %s usage does not permit "
+                             "certificate signing"), certFile);
+                return -1;
+            } else {
+                DPRINTF("Certificate %s usage does not permit certificate "
+                        "signing", certFile);
+            }
+        }
+    } else {
+        if (!(usage & GNUTLS_KEY_DIGITAL_SIGNATURE)) {
+            if (critical) {
+                error_setg(errp,
+                           _("Certificate %s usage does not permit digital "
+                             "signature"), certFile);
+                return -1;
+            } else {
+                DPRINTF("Certificate %s usage does not permit digital "
+                        "signature", certFile);
+            }
+        }
+        if (!(usage & GNUTLS_KEY_KEY_ENCIPHERMENT)) {
+            if (critical) {
+                error_setg(errp,
+                           _("Certificate %s usage does not permit key "
+                             "encipherment"), certFile);
+                return -1;
+            } else {
+                DPRINTF("Certificate %s usage does not permit key "
+                        "encipherment", certFile);
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+static int qcrypto_tls_creds_check_cert_key_purpose(gnutls_x509_crt_t cert,
+                                                    const char *certFile,
+                                                    bool isServer,
+                                                    Error **errp)
+{
+    int status;
+    size_t i;
+    unsigned int purposeCritical;
+    unsigned int critical;
+    char *buffer = NULL;
+    size_t size;
+    bool allowClient = false, allowServer = false;
+
+    critical = 0;
+    for (i = 0; ; i++) {
+        size = 0;
+        status = gnutls_x509_crt_get_key_purpose_oid(cert, i, buffer,
+                                                     &size, NULL);
+
+        if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
+            DPRINTF("No key purpose data available at slot %zu", i);
+
+            /* If there is no data at all, then we must allow
+               client/server to pass */
+            if (i == 0) {
+                allowServer = allowClient = true;
+            }
+            break;
+        }
+        if (status != GNUTLS_E_SHORT_MEMORY_BUFFER) {
+            error_setg(errp,
+                       _("Unable to query certificate %s key purpose %s"),
+                       certFile, gnutls_strerror(status));
+            return -1;
+        }
+
+        buffer = g_new0(char, size);
+
+        status = gnutls_x509_crt_get_key_purpose_oid(cert, i, buffer,
+                                                     &size, &purposeCritical);
+        if (status < 0) {
+            g_free(buffer);
+            error_setg(errp,
+                       _("Unable to query certificate %s key purpose %s"),
+                       certFile, gnutls_strerror(status));
+            return -1;
+        }
+        if (purposeCritical) {
+            critical = true;
+        }
+
+        DPRINTF("Key purpose %d %s critical %u",
+                status, buffer, purposeCritical);
+        if (g_str_equal(buffer, GNUTLS_KP_TLS_WWW_SERVER)) {
+            allowServer = true;
+        } else if (g_str_equal(buffer, GNUTLS_KP_TLS_WWW_CLIENT)) {
+            allowClient = true;
+        } else if (g_str_equal(buffer, GNUTLS_KP_ANY)) {
+            allowServer = allowClient = true;
+        }
+
+        g_free(buffer);
+    }
+
+    if (isServer) {
+        if (!allowServer) {
+            if (critical) {
+                error_setg(errp,
+                           _("Certificate %s purpose does not allow "
+                             "use for with a TLS server"), certFile);
+                return -1;
+            } else {
+                DPRINTF("Certificate %s purpose does not allow use for "
+                        "with a TLS server", certFile);
+            }
+        }
+    } else {
+        if (!allowClient) {
+            if (critical) {
+                error_setg(errp,
+                           _("Certificate %s purpose does not allow use "
+                             "for with a TLS client"), certFile);
+                return -1;
+            } else {
+                DPRINTF("Certificate %s purpose does not allow use for "
+                        "with a TLS client", certFile);
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+static int qcrypto_tls_creds_check_cert(gnutls_x509_crt_t cert,
+                                        const char *certFile,
+                                        bool isServer,
+                                        bool isCA,
+                                        Error **errp)
+{
+    if (qcrypto_tls_creds_check_cert_times(cert, certFile,
+                                           isServer, isCA,
+                                           errp) < 0) {
+        return -1;
+    }
+
+#if LIBGNUTLS_VERSION_NUMBER >= 2
+    if (qcrypto_tls_creds_check_cert_basic_constraints(cert, certFile,
+                                                       isServer, isCA,
+                                                       errp) < 0) {
+        return -1;
+    }
+#endif
+
+    if (qcrypto_tls_creds_check_cert_key_usage(cert, certFile,
+                                               isCA, errp) < 0) {
+        return -1;
+    }
+
+    if (!isCA &&
+        qcrypto_tls_creds_check_cert_key_purpose(cert, certFile,
+                                                 isServer, errp) < 0) {
+        return -1;
+    }
+
+    return 0;
+}
+
+static int qcrypto_tls_creds_check_cert_pair(gnutls_x509_crt_t cert,
+                                             const char *certFile,
+                                             gnutls_x509_crt_t *cacerts,
+                                             size_t ncacerts,
+                                             const char *cacertFile,
+                                             bool isServer,
+                                             Error **errp)
+{
+    unsigned int status;
+
+    if (gnutls_x509_crt_list_verify(&cert, 1,
+                                    cacerts, ncacerts,
+                                    NULL, 0,
+                                    0, &status) < 0) {
+        error_setg(errp, isServer ?
+                   _("Unable to verify server certificate %s against "
+                     "CA certificate %s") :
+                   _("Unable to verify client certificate %s against "
+                     "CA certificate %s"),
+                   certFile, cacertFile);
+        return -1;
+    }
+
+    if (status != 0) {
+        const char *reason = _("Invalid certificate");
+
+        if (status & GNUTLS_CERT_INVALID) {
+            reason = _("The certificate is not trusted.");
+        }
+
+        if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
+            reason = _("The certificate hasn't got a known issuer.");
+        }
+
+        if (status & GNUTLS_CERT_REVOKED) {
+            reason = _("The certificate has been revoked.");
+        }
+
+#ifndef GNUTLS_1_0_COMPAT
+        if (status & GNUTLS_CERT_INSECURE_ALGORITHM) {
+            reason = _("The certificate uses an insecure algorithm");
+        }
+#endif
+
+        error_setg(errp,
+                   _("Our own certificate %s failed validation against %s: %s"),
+                   certFile, cacertFile, reason);
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static gnutls_x509_crt_t qcrypto_tls_creds_load_cert(const char *certFile,
+                                                     bool isServer,
+                                                     Error **errp)
+{
+    gnutls_datum_t data;
+    gnutls_x509_crt_t cert = NULL;
+    char *buf = NULL;
+    gsize buflen;
+    GError *gerr;
+    int ret = -1;
+
+    DPRINTF("isServer %d certFile %s",
+              isServer, certFile);
+
+    if (gnutls_x509_crt_init(&cert) < 0) {
+        error_setg(errp, "%s",
+                   _("Unable to initialize certificate"));
+        goto cleanup;
+    }
+
+    if (!g_file_get_contents(certFile, &buf, &buflen, &gerr)) {
+        error_setg(errp, _("Cannot load CA cert list %s: %s"),
+                   certFile, gerr->message);
+        g_error_free(gerr);
+        goto cleanup;
+    }
+
+    data.data = (unsigned char *)buf;
+    data.size = strlen(buf);
+
+    if (gnutls_x509_crt_import(cert, &data, GNUTLS_X509_FMT_PEM) < 0) {
+        error_setg(errp, isServer ?
+                   _("Unable to import server certificate %s") :
+                   _("Unable to import client certificate %s"),
+                   certFile);
+        goto cleanup;
+    }
+
+    ret = 0;
+
+ cleanup:
+    if (ret != 0) {
+        gnutls_x509_crt_deinit(cert);
+        cert = NULL;
+    }
+    g_free(buf);
+    return cert;
+}
+
+
+static int qcrypto_tls_creds_load_ca_cert_list(const char *certFile,
+                                               gnutls_x509_crt_t *certs,
+                                               unsigned int certMax,
+                                               size_t *ncerts,
+                                               Error **errp)
+{
+    gnutls_datum_t data;
+    char *buf = NULL;
+    gsize buflen;
+    int ret = -1;
+    GError *gerr = NULL;
+
+    *ncerts = 0;
+    DPRINTF("certFile %s", certFile);
+
+    if (!g_file_get_contents(certFile, &buf, &buflen, &gerr)) {
+        error_setg(errp, _("Cannot load CA cert list %s: %s"),
+                   certFile, gerr->message);
+        g_error_free(gerr);
+        goto cleanup;
+    }
+
+    data.data = (unsigned char *)buf;
+    data.size = strlen(buf);
+
+    if (gnutls_x509_crt_list_import(certs, &certMax, &data,
+                                    GNUTLS_X509_FMT_PEM, 0) < 0) {
+        error_setg(errp,
+                   _("Unable to import CA certificate list %s"),
+                   certFile);
+        goto cleanup;
+    }
+    *ncerts = certMax;
+
+    ret = 0;
+
+ cleanup:
+    g_free(buf);
+    return ret;
+}
+
+
+#define MAX_CERTS 16
+static int qcrypto_tls_creds_sanity_check(bool isServer,
+                                          const char *cacertFile,
+                                          const char *certFile,
+                                          Error **errp)
+{
+    gnutls_x509_crt_t cert = NULL;
+    gnutls_x509_crt_t cacerts[MAX_CERTS];
+    size_t ncacerts = 0;
+    size_t i;
+    int ret = -1;
+
+    memset(cacerts, 0, sizeof(cacerts));
+    if (access(certFile, R_OK) == 0) {
+        cert = qcrypto_tls_creds_load_cert(certFile, isServer,
+                                           errp);
+        if (!cert) {
+            goto cleanup;
+        }
+    }
+    if (access(cacertFile, R_OK) == 0) {
+        if (qcrypto_tls_creds_load_ca_cert_list(cacertFile, cacerts,
+                                                MAX_CERTS, &ncacerts,
+                                                errp) < 0) {
+            goto cleanup;
+        }
+    }
+
+    if (cert &&
+        qcrypto_tls_creds_check_cert(cert, certFile, isServer,
+                                     false, errp) < 0) {
+        goto cleanup;
+    }
+
+    for (i = 0; i < ncacerts; i++) {
+        if (qcrypto_tls_creds_check_cert(cacerts[i], cacertFile,
+                                         isServer, true, errp) < 0) {
+            goto cleanup;
+        }
+    }
+
+    if (cert && ncacerts &&
+        qcrypto_tls_creds_check_cert_pair(cert, certFile, cacerts,
+                                          ncacerts, cacertFile,
+                                          isServer, errp) < 0) {
+        goto cleanup;
+    }
+
+    ret = 0;
+
+ cleanup:
+    if (cert) {
+        gnutls_x509_crt_deinit(cert);
+    }
+    for (i = 0; i < ncacerts; i++) {
+        gnutls_x509_crt_deinit(cacerts[i]);
+    }
+    return ret;
+}
 
 
 static int
@@ -204,6 +702,13 @@ qcrypto_tls_creds_load_x509(QCryptoTLSCreds *creds,
         }
     }
 
+    if (creds->sanityCheck &&
+        qcrypto_tls_creds_sanity_check(
+            creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
+            cacert, cert, errp) < 0) {
+        goto cleanup;
+    }
+
     ret = gnutls_certificate_allocate_credentials(&creds->data.x509);
     if (ret < 0) {
         error_setg(errp, _("Cannot allocate credentials '%s'"),
@@ -438,6 +943,24 @@ static bool qcrypto_tls_creds_prop_get_verify(Object *obj,
     return creds->verifyPeer;
 }
 
+static void qcrypto_tls_creds_prop_set_sanity(Object *obj,
+                                              bool value,
+                                              Error **errp G_GNUC_UNUSED)
+{
+    QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj);
+
+    creds->sanityCheck = value;
+}
+
+
+static bool qcrypto_tls_creds_prop_get_sanity(Object *obj,
+                                              Error **errp G_GNUC_UNUSED)
+{
+    QCryptoTLSCreds *creds = QCRYPTO_TLS_CREDS(obj);
+
+    return creds->sanityCheck;
+}
+
 static void qcrypto_tls_creds_prop_set_dir(Object *obj,
                                            const char *value,
                                            Error **errp G_GNUC_UNUSED)
@@ -506,6 +1029,10 @@ static void qcrypto_tls_creds_init(Object *obj G_GNUC_UNUSED)
                              qcrypto_tls_creds_prop_get_verify,
                              qcrypto_tls_creds_prop_set_verify,
                              NULL);
+    object_property_add_bool(obj, "sanity-check",
+                             qcrypto_tls_creds_prop_get_sanity,
+                             qcrypto_tls_creds_prop_set_sanity,
+                             NULL);
     object_property_add_str(obj, "dir",
                             qcrypto_tls_creds_prop_get_dir,
                             qcrypto_tls_creds_prop_set_dir,
diff --git a/include/crypto/tlscreds.h b/include/crypto/tlscreds.h
index c9d2009..a6700ec 100644
--- a/include/crypto/tlscreds.h
+++ b/include/crypto/tlscreds.h
@@ -122,6 +122,7 @@ struct QCryptoTLSCreds {
     gnutls_dh_params_t dh_params;
 #endif
     bool verifyPeer;
+    bool sanityCheck;
 };
 
 struct QCryptoTLSCredsClass {
diff --git a/tests/.gitignore b/tests/.gitignore
index e93148e..ad64e38 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -11,6 +11,9 @@ test-bitops
 test-coroutine
 test-crypto-cipher
 test-crypto-hash
+test-crypto-tlscreds
+test-crypto-tlscreds-work/
+test-crypto-tlscreds-certdir/
 test-cutils
 test-hbitmap
 test-int128
diff --git a/tests/Makefile b/tests/Makefile
index e920dae..d3aa188 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -74,6 +74,7 @@ check-unit-y += tests/test-write-threshold$(EXESUF)
 gcov-files-test-write-threshold-y = block/write-threshold.c
 check-unit-$(CONFIG_GNUTLS_HASH) += tests/test-crypto-hash$(EXESUF)
 check-unit-y += tests/test-crypto-cipher$(EXESUF)
+check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlscreds$(EXESUF)
 
 check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
 
@@ -313,6 +314,8 @@ tests/test-mul64$(EXESUF): tests/test-mul64.o libqemuutil.a
 tests/test-bitops$(EXESUF): tests/test-bitops.o libqemuutil.a
 tests/test-crypto-hash$(EXESUF): tests/test-crypto-hash.o $(qom-core-obj) libqemuutil.a libqemustub.a
 tests/test-crypto-cipher$(EXESUF): tests/test-crypto-cipher.o $(qom-core-obj) libqemuutil.a libqemustub.a
+tests/test-crypto-tlscreds$(EXESUF): tests/test-crypto-tlscreds.o tests/crypto-tls-helpers.o tests/pkix_asn1_tab.o \
+        crypto/tlscreds.o $(qom-core-obj) libqemuutil.a libqemustub.a
 
 libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o
 libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o
@@ -369,7 +372,7 @@ tests/usb-hcd-uhci-test$(EXESUF): tests/usb-hcd-uhci-test.o $(libqos-usb-obj-y)
 tests/usb-hcd-ehci-test$(EXESUF): tests/usb-hcd-ehci-test.o $(libqos-usb-obj-y)
 tests/usb-hcd-xhci-test$(EXESUF): tests/usb-hcd-xhci-test.o $(libqos-usb-obj-y)
 tests/pc-cpu-test$(EXESUF): tests/pc-cpu-test.o
-tests/vhost-user-test$(EXESUF): tests/vhost-user-test.o qemu-char.o qemu-timer.o $(qtest-obj-y)
+tests/vhost-user-test$(EXESUF): tests/vhost-user-test.o qemu-char.o qemu-timer.o $(qtest-obj-y) $(qom-core-obj) libqemuutil.a libqemustub.a
 tests/qemu-iotests/socket_scm_helper$(EXESUF): tests/qemu-iotests/socket_scm_helper.o
 tests/test-qemu-opts$(EXESUF): tests/test-qemu-opts.o libqemuutil.a libqemustub.a
 tests/test-write-threshold$(EXESUF): tests/test-write-threshold.o $(block-obj-y) libqemuutil.a libqemustub.a
@@ -377,6 +380,8 @@ tests/test-write-threshold$(EXESUF): tests/test-write-threshold.o $(block-obj-y)
 ifeq ($(CONFIG_POSIX),y)
 LIBS += -lutil
 endif
+LIBS += $(TEST_LIBS)
+CFLAGS += $(TEST_CFLAGS)
 
 # QTest rules
 
diff --git a/tests/crypto-tls-helpers.c b/tests/crypto-tls-helpers.c
new file mode 100644
index 0000000..0ea335c
--- /dev/null
+++ b/tests/crypto-tls-helpers.c
@@ -0,0 +1,485 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daniel P. Berrange <berrange@redhat.com>
+ */
+
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+
+#include "config-host.h"
+#include "crypto-tls-helpers.h"
+#include "qemu/sockets.h"
+
+#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
+
+/*
+ * This stores some static data that is needed when
+ * encoding extensions in the x509 certs
+ */
+ASN1_TYPE pkix_asn1;
+
+/*
+ * To avoid consuming random entropy to generate keys,
+ * here's one we prepared earlier :-)
+ */
+gnutls_x509_privkey_t privkey;
+# define PRIVATE_KEY                                              \
+    "-----BEGIN PRIVATE KEY-----\n"                               \
+    "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBALVcr\n"     \
+    "BL40Tm6yq88FBhJNw1aaoCjmtg0l4dWQZ/e9Fimx4ARxFpT+ji4FE\n"     \
+    "Cgl9s/SGqC+1nvlkm9ViSo0j7MKDbnDB+VRHDvMAzQhA2X7e8M0n9\n"     \
+    "rPolUY2lIVC83q0BBaOBkCj2RSmT2xTEbbC2xLukSrg2WP/ihVOxc\n"     \
+    "kXRuyFtzAgMBAAECgYB7slBexDwXrtItAMIH6m/U+LUpNe0Xx48OL\n"     \
+    "IOn4a4whNgO/o84uIwygUK27ZGFZT0kAGAk8CdF9hA6ArcbQ62s1H\n"     \
+    "myxrUbF9/mrLsQw1NEqpuUk9Ay2Tx5U/wPx35S3W/X2AvR/ZpTnCn\n"     \
+    "2q/7ym9fyiSoj86drD7BTvmKXlOnOwQJBAPOFMp4mMa9NGpGuEssO\n"     \
+    "m3Uwbp6lhcP0cA9MK+iOmeANpoKWfBdk5O34VbmeXnGYWEkrnX+9J\n"     \
+    "bM4wVhnnBWtgBMCQQC+qAEmvwcfhauERKYznMVUVksyeuhxhCe7EK\n"     \
+    "mPh+U2+g0WwdKvGDgO0PPt1gq0ILEjspMDeMHVdTwkaVBo/uMhAkA\n"     \
+    "Z5SsZyCP2aTOPFDypXRdI4eqRcjaEPOUBq27r3uYb/jeboVb2weLa\n"     \
+    "L1MmVuHiIHoa5clswPdWVI2y0em2IGoDAkBPSp/v9VKJEZabk9Frd\n"     \
+    "a+7u4fanrM9QrEjY3KhduslSilXZZSxrWjjAJPyPiqFb3M8XXA26W\n"     \
+    "nz1KYGnqYKhLcBAkB7dt57n9xfrhDpuyVEv+Uv1D3VVAhZlsaZ5Pp\n"     \
+    "dcrhrkJn2sa/+O8OKvdrPSeeu/N5WwYhJf61+CPoenMp7IFci\n"         \
+    "-----END PRIVATE KEY-----\n"
+
+/*
+ * This loads the private key we defined earlier
+ */
+static gnutls_x509_privkey_t test_tls_load_key(void)
+{
+    gnutls_x509_privkey_t key;
+    const gnutls_datum_t data = { (unsigned char *)PRIVATE_KEY,
+                                  strlen(PRIVATE_KEY) };
+    int err;
+
+    err = gnutls_x509_privkey_init(&key);
+    if (err < 0) {
+        g_critical("Failed to init key %s", gnutls_strerror(err));
+        abort();
+    }
+
+    err = gnutls_x509_privkey_import(key, &data,
+                                     GNUTLS_X509_FMT_PEM);
+    if (err < 0) {
+        if (err != GNUTLS_E_BASE64_UNEXPECTED_HEADER_ERROR) {
+            g_critical("Failed to import key %s", gnutls_strerror(err));
+            abort();
+        }
+
+        err = gnutls_x509_privkey_import_pkcs8(
+            key, &data, GNUTLS_X509_FMT_PEM, NULL, 0);
+        if (err < 0) {
+            g_critical("Failed to import PKCS8 key %s", gnutls_strerror(err));
+            abort();
+        }
+    }
+
+    return key;
+}
+
+
+void test_tls_init(const char *keyfile)
+{
+    gnutls_global_init();
+
+    if (asn1_array2tree(pkix_asn1_tab, &pkix_asn1, NULL) != ASN1_SUCCESS) {
+        abort();
+    }
+
+    privkey = test_tls_load_key();
+    if (!g_file_set_contents(keyfile, PRIVATE_KEY, -1, NULL)) {
+        abort();
+    }
+}
+
+
+void test_tls_cleanup(const char *keyfile)
+{
+    asn1_delete_structure(&pkix_asn1);
+    unlink(keyfile);
+}
+
+/*
+ * Turns an ASN1 object into a DER encoded byte array
+ */
+static void test_tls_der_encode(ASN1_TYPE src,
+                                const char *src_name,
+                                gnutls_datum_t *res)
+{
+  int size;
+  char *data = NULL;
+
+  size = 0;
+  asn1_der_coding(src, src_name, NULL, &size, NULL);
+
+  data = g_new0(char, size);
+
+  asn1_der_coding(src, src_name, data, &size, NULL);
+
+  res->data = (unsigned char *)data;
+  res->size = size;
+}
+
+
+static void
+test_tls_get_ipaddr(const char *addrstr,
+                    char **data,
+                    int *datalen)
+{
+    struct addrinfo *res;
+    struct addrinfo hints;
+
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_flags = AI_NUMERICHOST;
+    g_assert(getaddrinfo(addrstr, NULL, &hints, &res) == 0);
+
+    *datalen = res->ai_addrlen;
+    *data = g_new(char, *datalen);
+    memcpy(*data, res->ai_addr, *datalen);
+}
+
+/*
+ * This is a fairly lame x509 certificate generator.
+ *
+ * Do not copy/use this code for generating real certificates
+ * since it leaves out many things that you would want in
+ * certificates for real world usage.
+ *
+ * This is good enough only for doing tests of the QEMU
+ * TLS certificate code
+ */
+void
+test_tls_generate_cert(QCryptoTLSTestCertReq *req,
+                       gnutls_x509_crt_t ca)
+{
+    gnutls_x509_crt_t crt;
+    int err;
+    static char buffer[1024*1024];
+    size_t size = sizeof(buffer);
+    char serial[5] = { 1, 2, 3, 4, 0 };
+    gnutls_datum_t der;
+    time_t start = time(NULL) + (60*60*req->start_offset);
+    time_t expire = time(NULL) + (60*60*(req->expire_offset
+                                         ? req->expire_offset : 24));
+
+    /*
+     * Prepare our new certificate object
+     */
+    err = gnutls_x509_crt_init(&crt);
+    if (err < 0) {
+        g_critical("Failed to initialize certificate %s", gnutls_strerror(err));
+        abort();
+    }
+    err = gnutls_x509_crt_set_key(crt, privkey);
+    if (err < 0) {
+        g_critical("Failed to set certificate key %s", gnutls_strerror(err));
+        abort();
+    }
+
+    /*
+     * A v3 certificate is required in order to be able
+     * set any of the basic constraints, key purpose and
+     * key usage data
+     */
+    gnutls_x509_crt_set_version(crt, 3);
+
+    if (req->country) {
+        err = gnutls_x509_crt_set_dn_by_oid(
+            crt, GNUTLS_OID_X520_COUNTRY_NAME, 0,
+            req->country, strlen(req->country));
+        if (err < 0) {
+            g_critical("Failed to set certificate country name %s",
+                       gnutls_strerror(err));
+            abort();
+        }
+    }
+    if (req->cn) {
+        err = gnutls_x509_crt_set_dn_by_oid(
+            crt, GNUTLS_OID_X520_COMMON_NAME, 0,
+            req->cn, strlen(req->cn));
+        if (err < 0) {
+            g_critical("Failed to set certificate common name %s",
+                       gnutls_strerror(err));
+            abort();
+        }
+    }
+
+    /*
+     * Setup the subject altnames, which are used
+     * for hostname checks in live sessions
+     */
+    if (req->altname1) {
+        err = gnutls_x509_crt_set_subject_alt_name(
+            crt, GNUTLS_SAN_DNSNAME,
+            req->altname1,
+            strlen(req->altname1),
+            GNUTLS_FSAN_APPEND);
+        if (err < 0) {
+            g_critical("Failed to set certificate alt name %s",
+                       gnutls_strerror(err));
+            abort();
+        }
+    }
+    if (req->altname2) {
+        err = gnutls_x509_crt_set_subject_alt_name(
+            crt, GNUTLS_SAN_DNSNAME,
+            req->altname2,
+            strlen(req->altname2),
+            GNUTLS_FSAN_APPEND);
+        if (err < 0) {
+            g_critical("Failed to set certificate %s alt name",
+                       gnutls_strerror(err));
+            abort();
+        }
+    }
+
+    /*
+     * IP address need to be put into the cert in their
+     * raw byte form, not strings, hence this is a little
+     * more complicated
+     */
+    if (req->ipaddr1) {
+        char *data;
+        int len;
+
+        test_tls_get_ipaddr(req->ipaddr1, &data, &len);
+
+        err = gnutls_x509_crt_set_subject_alt_name(
+            crt, GNUTLS_SAN_IPADDRESS,
+            data, len, GNUTLS_FSAN_APPEND);
+        if (err < 0) {
+            g_critical("Failed to set certificate alt name %s",
+                       gnutls_strerror(err));
+            abort();
+        }
+        g_free(data);
+    }
+    if (req->ipaddr2) {
+        char *data;
+        int len;
+
+        test_tls_get_ipaddr(req->ipaddr2, &data, &len);
+
+        err = gnutls_x509_crt_set_subject_alt_name(
+            crt, GNUTLS_SAN_IPADDRESS,
+            data, len, GNUTLS_FSAN_APPEND);
+        if (err < 0) {
+            g_critical("Failed to set certificate alt name %s",
+                       gnutls_strerror(err));
+            abort();
+        }
+        g_free(data);
+    }
+
+
+    /*
+     * Basic constraints are used to decide if the cert
+     * is for a CA or not. We can't use the convenient
+     * gnutls API for setting this, since it hardcodes
+     * the 'critical' field which we want control over
+     */
+    if (req->basicConstraintsEnable) {
+        ASN1_TYPE ext = ASN1_TYPE_EMPTY;
+
+        asn1_create_element(pkix_asn1, "PKIX1.BasicConstraints", &ext);
+        asn1_write_value(ext, "cA",
+                         req->basicConstraintsIsCA ? "TRUE" : "FALSE", 1);
+        asn1_write_value(ext, "pathLenConstraint", NULL, 0);
+        test_tls_der_encode(ext, "", &der);
+        err = gnutls_x509_crt_set_extension_by_oid(
+            crt, "2.5.29.19",
+            der.data, der.size,
+            req->basicConstraintsCritical);
+        if (err < 0) {
+            g_critical("Failed to set certificate basic constraints %s",
+                       gnutls_strerror(err));
+            g_free(der.data);
+            abort();
+        }
+        asn1_delete_structure(&ext);
+        g_free(der.data);
+    }
+
+    /*
+     * Next up the key usage extension. Again we can't
+     * use the gnutls API since it hardcodes the extension
+     * to be 'critical'
+     */
+    if (req->keyUsageEnable) {
+        ASN1_TYPE ext = ASN1_TYPE_EMPTY;
+        char str[2];
+
+        str[0] = req->keyUsageValue & 0xff;
+        str[1] = (req->keyUsageValue >> 8) & 0xff;
+
+        asn1_create_element(pkix_asn1, "PKIX1.KeyUsage", &ext);
+        asn1_write_value(ext, "", str, 9);
+        test_tls_der_encode(ext, "", &der);
+        err = gnutls_x509_crt_set_extension_by_oid(
+            crt, "2.5.29.15",
+            der.data, der.size,
+            req->keyUsageCritical);
+        if (err < 0) {
+            g_critical("Failed to set certificate key usage %s",
+                       gnutls_strerror(err));
+            g_free(der.data);
+            abort();
+        }
+        asn1_delete_structure(&ext);
+        g_free(der.data);
+    }
+
+    /*
+     * Finally the key purpose extension. This time
+     * gnutls has the opposite problem, always hardcoding
+     * it to be non-critical. So once again we have to
+     * set this the hard way building up ASN1 data ourselves
+     */
+    if (req->keyPurposeEnable) {
+        ASN1_TYPE ext = ASN1_TYPE_EMPTY;
+
+        asn1_create_element(pkix_asn1, "PKIX1.ExtKeyUsageSyntax", &ext);
+        if (req->keyPurposeOID1) {
+            asn1_write_value(ext, "", "NEW", 1);
+            asn1_write_value(ext, "?LAST", req->keyPurposeOID1, 1);
+        }
+        if (req->keyPurposeOID2) {
+            asn1_write_value(ext, "", "NEW", 1);
+            asn1_write_value(ext, "?LAST", req->keyPurposeOID2, 1);
+        }
+        test_tls_der_encode(ext, "", &der);
+        err = gnutls_x509_crt_set_extension_by_oid(
+            crt, "2.5.29.37",
+            der.data, der.size,
+            req->keyPurposeCritical);
+        if (err < 0) {
+            g_critical("Failed to set certificate key purpose %s",
+                       gnutls_strerror(err));
+            g_free(der.data);
+            abort();
+        }
+        asn1_delete_structure(&ext);
+        g_free(der.data);
+    }
+
+    /*
+     * Any old serial number will do, so lets pick 5
+     */
+    err = gnutls_x509_crt_set_serial(crt, serial, 5);
+    if (err < 0) {
+        g_critical("Failed to set certificate serial %s",
+                   gnutls_strerror(err));
+        abort();
+    }
+
+    err = gnutls_x509_crt_set_activation_time(crt, start);
+    if (err < 0) {
+        g_critical("Failed to set certificate activation %s",
+                   gnutls_strerror(err));
+        abort();
+    }
+    err = gnutls_x509_crt_set_expiration_time(crt, expire);
+    if (err < 0) {
+        g_critical("Failed to set certificate expiration %s",
+                   gnutls_strerror(err));
+        abort();
+    }
+
+
+    /*
+     * If no 'ca' is set then we are self signing
+     * the cert. This is done for the root CA certs
+     */
+    err = gnutls_x509_crt_sign(crt, ca ? ca : crt, privkey);
+    if (err < 0) {
+        g_critical("Failed to sign certificate %s",
+                   gnutls_strerror(err));
+        abort();
+    }
+
+    /*
+     * Finally write the new cert out to disk
+     */
+    err = gnutls_x509_crt_export(
+        crt, GNUTLS_X509_FMT_PEM, buffer, &size);
+    if (err < 0) {
+        g_critical("Failed to export certificate %s: %d",
+                   gnutls_strerror(err), err);
+        abort();
+    }
+
+    if (!g_file_set_contents(req->filename, buffer, -1, NULL)) {
+        g_critical("Failed to write certificate %s",
+                   req->filename);
+        abort();
+    }
+
+    req->crt = crt;
+}
+
+
+void test_tls_write_cert_chain(const char *filename,
+                               gnutls_x509_crt_t *certs,
+                               size_t ncerts)
+{
+    size_t i;
+    size_t capacity = 1024, offset = 0;
+    char *buffer = g_new0(char, capacity);
+    int err;
+
+    for (i = 0; i < ncerts; i++) {
+        size_t len = capacity - offset;
+    retry:
+        err = gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
+                                     buffer + offset, &len);
+        if (err < 0) {
+            if (err == GNUTLS_E_SHORT_MEMORY_BUFFER) {
+                buffer = g_renew(char, buffer, offset + len);
+                capacity = offset + len;
+                goto retry;
+            }
+            g_critical("Failed to export certificate chain %s: %d",
+                       gnutls_strerror(err), err);
+            abort();
+        }
+        offset += len;
+    }
+
+    if (!g_file_set_contents(filename, buffer, offset, NULL)) {
+        abort();
+    }
+}
+
+
+void test_tls_discard_cert(QCryptoTLSTestCertReq *req)
+{
+    if (!req->crt) {
+        return;
+    }
+
+    gnutls_x509_crt_deinit(req->crt);
+    req->crt = NULL;
+
+    if (getenv("QEMU_TEST_DEBUG_CERTS") == NULL) {
+        unlink(req->filename);
+    }
+}
+
+#endif /* QCRYPTO_HAVE_TLS_TEST_SUPPORT */
diff --git a/tests/crypto-tls-helpers.h b/tests/crypto-tls-helpers.h
new file mode 100644
index 0000000..356b49c
--- /dev/null
+++ b/tests/crypto-tls-helpers.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daniel P. Berrange <berrange@redhat.com>
+ */
+
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+
+#if !(defined WIN32) && \
+    defined(CONFIG_TASN1) && \
+    defined(LIBGNUTLS_VERSION_NUMBER) && \
+    (LIBGNUTLS_VERSION_NUMBER >= 0x020600)
+# define QCRYPTO_HAVE_TLS_TEST_SUPPORT
+#endif
+
+#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
+# include <libtasn1.h>
+
+# include "qemu-common.h"
+
+/*
+ * This contains parameter about how to generate
+ * certificates.
+ */
+typedef struct QCryptoTLSTestCertReq QCryptoTLSTestCertReq;
+struct QCryptoTLSTestCertReq {
+    gnutls_x509_crt_t crt;
+
+    const char *filename;
+
+    /* Identifying information */
+    const char *country;
+    const char *cn;
+    const char *altname1;
+    const char *altname2;
+    const char *ipaddr1;
+    const char *ipaddr2;
+
+    /* Basic constraints */
+    bool basicConstraintsEnable;
+    bool basicConstraintsCritical;
+    bool basicConstraintsIsCA;
+
+    /* Key usage */
+    bool keyUsageEnable;
+    bool keyUsageCritical;
+    int keyUsageValue;
+
+    /* Key purpose (aka Extended key usage) */
+    bool keyPurposeEnable;
+    bool keyPurposeCritical;
+    const char *keyPurposeOID1;
+    const char *keyPurposeOID2;
+
+    /* zero for current time, or non-zero for hours from now */
+    int start_offset;
+    /* zero for 24 hours from now, or non-zero for hours from now */
+    int expire_offset;
+};
+
+void test_tls_generate_cert(QCryptoTLSTestCertReq *req,
+                            gnutls_x509_crt_t ca);
+void test_tls_write_cert_chain(const char *filename,
+                               gnutls_x509_crt_t *certs,
+                               size_t ncerts);
+void test_tls_discard_cert(QCryptoTLSTestCertReq *req);
+
+void test_tls_init(const char *keyfile);
+void test_tls_cleanup(const char *keyfile);
+
+# define TLS_CERT_REQ(varname, cavarname,                               \
+                      country, commonname,                              \
+                      altname1, altname2,                               \
+                      ipaddr1, ipaddr2,                                 \
+                      basicconsenable, basicconscritical, basicconsca,  \
+                      keyusageenable, keyusagecritical, keyusagevalue,  \
+                      keypurposeenable, keypurposecritical,             \
+                      keypurposeoid1, keypurposeoid2,                   \
+                      startoffset, endoffset)                           \
+    static QCryptoTLSTestCertReq varname = {                            \
+        NULL, WORKDIR #varname "-ctx.pem",                              \
+        country, commonname, altname1, altname2,                        \
+        ipaddr1, ipaddr2,                                               \
+        basicconsenable, basicconscritical, basicconsca,                \
+        keyusageenable, keyusagecritical, keyusagevalue,                \
+        keypurposeenable, keypurposecritical,                           \
+        keypurposeoid1, keypurposeoid2,                                 \
+        startoffset, endoffset                                          \
+    };                                                                  \
+    test_tls_generate_cert(&varname, cavarname.crt)
+
+# define TLS_ROOT_REQ(varname,                                          \
+                      country, commonname,                              \
+                      altname1, altname2,                               \
+                      ipaddr1, ipaddr2,                                 \
+                      basicconsenable, basicconscritical, basicconsca,  \
+                      keyusageenable, keyusagecritical, keyusagevalue,  \
+                      keypurposeenable, keypurposecritical,             \
+                      keypurposeoid1, keypurposeoid2,                   \
+                      startoffset, endoffset)                           \
+    static QCryptoTLSTestCertReq varname = {                            \
+        NULL, WORKDIR #varname "-ctx.pem",                              \
+        country, commonname, altname1, altname2,                        \
+        ipaddr1, ipaddr2,                                               \
+        basicconsenable, basicconscritical, basicconsca,                \
+        keyusageenable, keyusagecritical, keyusagevalue,                \
+        keypurposeenable, keypurposecritical,                           \
+        keypurposeoid1, keypurposeoid2,                                 \
+        startoffset, endoffset                                          \
+    };                                                                  \
+    test_tls_generate_cert(&varname, NULL)
+
+extern const ASN1_ARRAY_TYPE pkix_asn1_tab[];
+
+#endif /* QCRYPTO_HAVE_TLS_TEST_SUPPORT */
diff --git a/tests/pkix_asn1_tab.c b/tests/pkix_asn1_tab.c
new file mode 100644
index 0000000..9f89d89
--- /dev/null
+++ b/tests/pkix_asn1_tab.c
@@ -0,0 +1,1103 @@
+/*
+ * This file is taken from gnutls 1.6.3 under the GPLv2+
+ */
+
+#include <libtasn1.h>
+
+const ASN1_ARRAY_TYPE pkix_asn1_tab[] = {
+  {"PKIX1", 536875024, 0},
+  {0, 1073741836, 0},
+  {"id-ce", 1879048204, 0},
+  {"joint-iso-ccitt", 1073741825, "2"},
+  {"ds", 1073741825, "5"},
+  {0, 1, "29"},
+  {"id-ce-authorityKeyIdentifier", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "35"},
+  {"AuthorityKeyIdentifier", 1610612741, 0},
+  {"keyIdentifier", 1610637314, "KeyIdentifier"},
+  {0, 4104, "0"},
+  {"authorityCertIssuer", 1610637314, "GeneralNames"},
+  {0, 4104, "1"},
+  {"authorityCertSerialNumber", 536895490, "CertificateSerialNumber"},
+  {0, 4104, "2"},
+  {"KeyIdentifier", 1073741831, 0},
+  {"id-ce-subjectKeyIdentifier", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "14"},
+  {"SubjectKeyIdentifier", 1073741826, "KeyIdentifier"},
+  {"id-ce-keyUsage", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "15"},
+  {"KeyUsage", 1610874886, 0},
+  {"digitalSignature", 1073741825, "0"},
+  {"nonRepudiation", 1073741825, "1"},
+  {"keyEncipherment", 1073741825, "2"},
+  {"dataEncipherment", 1073741825, "3"},
+  {"keyAgreement", 1073741825, "4"},
+  {"keyCertSign", 1073741825, "5"},
+  {"cRLSign", 1073741825, "6"},
+  {"encipherOnly", 1073741825, "7"},
+  {"decipherOnly", 1, "8"},
+  {"id-ce-privateKeyUsagePeriod", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "16"},
+  {"PrivateKeyUsagePeriod", 1610612741, 0},
+  {"notBefore", 1619025937, 0},
+  {0, 4104, "0"},
+  {"notAfter", 545284113, 0},
+  {0, 4104, "1"},
+  {"id-ce-certificatePolicies", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "32"},
+  {"CertificatePolicies", 1612709899, 0},
+  {"MAX", 1074266122, "1"},
+  {0, 2, "PolicyInformation"},
+  {"PolicyInformation", 1610612741, 0},
+  {"policyIdentifier", 1073741826, "CertPolicyId"},
+  {"policyQualifiers", 538984459, 0},
+  {"MAX", 1074266122, "1"},
+  {0, 2, "PolicyQualifierInfo"},
+  {"CertPolicyId", 1073741836, 0},
+  {"PolicyQualifierInfo", 1610612741, 0},
+  {"policyQualifierId", 1073741826, "PolicyQualifierId"},
+  {"qualifier", 541065229, 0},
+  {"policyQualifierId", 1, 0},
+  {"PolicyQualifierId", 1073741836, 0},
+  {"CPSuri", 1073741826, "IA5String"},
+  {"UserNotice", 1610612741, 0},
+  {"noticeRef", 1073758210, "NoticeReference"},
+  {"explicitText", 16386, "DisplayText"},
+  {"NoticeReference", 1610612741, 0},
+  {"organization", 1073741826, "DisplayText"},
+  {"noticeNumbers", 536870923, 0},
+  {0, 3, 0},
+  {"DisplayText", 1610612754, 0},
+  {"visibleString", 1612709890, "VisibleString"},
+  {"200", 524298, "1"},
+  {"bmpString", 1612709890, "BMPString"},
+  {"200", 524298, "1"},
+  {"utf8String", 538968066, "UTF8String"},
+  {"200", 524298, "1"},
+  {"id-ce-policyMappings", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "33"},
+  {"PolicyMappings", 1612709899, 0},
+  {"MAX", 1074266122, "1"},
+  {0, 536870917, 0},
+  {"issuerDomainPolicy", 1073741826, "CertPolicyId"},
+  {"subjectDomainPolicy", 2, "CertPolicyId"},
+  {"DirectoryString", 1610612754, 0},
+  {"teletexString", 1612709890, "TeletexString"},
+  {"MAX", 524298, "1"},
+  {"printableString", 1612709890, "PrintableString"},
+  {"MAX", 524298, "1"},
+  {"universalString", 1612709890, "UniversalString"},
+  {"MAX", 524298, "1"},
+  {"utf8String", 1612709890, "UTF8String"},
+  {"MAX", 524298, "1"},
+  {"bmpString", 1612709890, "BMPString"},
+  {"MAX", 524298, "1"},
+  {"ia5String", 538968066, "IA5String"},
+  {"MAX", 524298, "1"},
+  {"id-ce-subjectAltName", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "17"},
+  {"SubjectAltName", 1073741826, "GeneralNames"},
+  {"GeneralNames", 1612709899, 0},
+  {"MAX", 1074266122, "1"},
+  {0, 2, "GeneralName"},
+  {"GeneralName", 1610612754, 0},
+  {"otherName", 1610620930, "AnotherName"},
+  {0, 4104, "0"},
+  {"rfc822Name", 1610620930, "IA5String"},
+  {0, 4104, "1"},
+  {"dNSName", 1610620930, "IA5String"},
+  {0, 4104, "2"},
+  {"x400Address", 1610620930, "ORAddress"},
+  {0, 4104, "3"},
+  {"directoryName", 1610620930, "RDNSequence"},
+  {0, 2056, "4"},
+  {"ediPartyName", 1610620930, "EDIPartyName"},
+  {0, 4104, "5"},
+  {"uniformResourceIdentifier", 1610620930, "IA5String"},
+  {0, 4104, "6"},
+  {"iPAddress", 1610620935, 0},
+  {0, 4104, "7"},
+  {"registeredID", 536879116, 0},
+  {0, 4104, "8"},
+  {"AnotherName", 1610612741, 0},
+  {"type-id", 1073741836, 0},
+  {"value", 541073421, 0},
+  {0, 1073743880, "0"},
+  {"type-id", 1, 0},
+  {"EDIPartyName", 1610612741, 0},
+  {"nameAssigner", 1610637314, "DirectoryString"},
+  {0, 4104, "0"},
+  {"partyName", 536879106, "DirectoryString"},
+  {0, 4104, "1"},
+  {"id-ce-issuerAltName", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "18"},
+  {"IssuerAltName", 1073741826, "GeneralNames"},
+  {"id-ce-subjectDirectoryAttributes", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "9"},
+  {"SubjectDirectoryAttributes", 1612709899, 0},
+  {"MAX", 1074266122, "1"},
+  {0, 2, "Attribute"},
+  {"id-ce-basicConstraints", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "19"},
+  {"BasicConstraints", 1610612741, 0},
+  {"cA", 1610645508, 0},
+  {0, 131081, 0},
+  {"pathLenConstraint", 537411587, 0},
+  {"0", 10, "MAX"},
+  {"id-ce-nameConstraints", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "30"},
+  {"NameConstraints", 1610612741, 0},
+  {"permittedSubtrees", 1610637314, "GeneralSubtrees"},
+  {0, 4104, "0"},
+  {"excludedSubtrees", 536895490, "GeneralSubtrees"},
+  {0, 4104, "1"},
+  {"GeneralSubtrees", 1612709899, 0},
+  {"MAX", 1074266122, "1"},
+  {0, 2, "GeneralSubtree"},
+  {"GeneralSubtree", 1610612741, 0},
+  {"base", 1073741826, "GeneralName"},
+  {"minimum", 1610653698, "BaseDistance"},
+  {0, 1073741833, "0"},
+  {0, 4104, "0"},
+  {"maximum", 536895490, "BaseDistance"},
+  {0, 4104, "1"},
+  {"BaseDistance", 1611137027, 0},
+  {"0", 10, "MAX"},
+  {"id-ce-policyConstraints", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "36"},
+  {"PolicyConstraints", 1610612741, 0},
+  {"requireExplicitPolicy", 1610637314, "SkipCerts"},
+  {0, 4104, "0"},
+  {"inhibitPolicyMapping", 536895490, "SkipCerts"},
+  {0, 4104, "1"},
+  {"SkipCerts", 1611137027, 0},
+  {"0", 10, "MAX"},
+  {"id-ce-cRLDistributionPoints", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "31"},
+  {"CRLDistributionPoints", 1612709899, 0},
+  {"MAX", 1074266122, "1"},
+  {0, 2, "DistributionPoint"},
+  {"DistributionPoint", 1610612741, 0},
+  {"distributionPoint", 1610637314, "DistributionPointName"},
+  {0, 2056, "0"},
+  {"reasons", 1610637314, "ReasonFlags"},
+  {0, 4104, "1"},
+  {"cRLIssuer", 536895490, "GeneralNames"},
+  {0, 4104, "2"},
+  {"DistributionPointName", 1610612754, 0},
+  {"fullName", 1610620930, "GeneralNames"},
+  {0, 4104, "0"},
+  {"nameRelativeToCRLIssuer", 536879106, "RelativeDistinguishedName"},
+  {0, 4104, "1"},
+  {"ReasonFlags", 1610874886, 0},
+  {"unused", 1073741825, "0"},
+  {"keyCompromise", 1073741825, "1"},
+  {"cACompromise", 1073741825, "2"},
+  {"affiliationChanged", 1073741825, "3"},
+  {"superseded", 1073741825, "4"},
+  {"cessationOfOperation", 1073741825, "5"},
+  {"certificateHold", 1073741825, "6"},
+  {"privilegeWithdrawn", 1073741825, "7"},
+  {"aACompromise", 1, "8"},
+  {"id-ce-extKeyUsage", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "37"},
+  {"ExtKeyUsageSyntax", 1612709899, 0},
+  {"MAX", 1074266122, "1"},
+  {0, 2, "KeyPurposeId"},
+  {"KeyPurposeId", 1073741836, 0},
+  {"id-kp-serverAuth", 1879048204, 0},
+  {0, 1073741825, "id-kp"},
+  {0, 1, "1"},
+  {"id-kp-clientAuth", 1879048204, 0},
+  {0, 1073741825, "id-kp"},
+  {0, 1, "2"},
+  {"id-kp-codeSigning", 1879048204, 0},
+  {0, 1073741825, "id-kp"},
+  {0, 1, "3"},
+  {"id-kp-emailProtection", 1879048204, 0},
+  {0, 1073741825, "id-kp"},
+  {0, 1, "4"},
+  {"id-kp-ipsecEndSystem", 1879048204, 0},
+  {0, 1073741825, "id-kp"},
+  {0, 1, "5"},
+  {"id-kp-ipsecTunnel", 1879048204, 0},
+  {0, 1073741825, "id-kp"},
+  {0, 1, "6"},
+  {"id-kp-ipsecUser", 1879048204, 0},
+  {0, 1073741825, "id-kp"},
+  {0, 1, "7"},
+  {"id-kp-timeStamping", 1879048204, 0},
+  {0, 1073741825, "id-kp"},
+  {0, 1, "8"},
+  {"id-pe-authorityInfoAccess", 1879048204, 0},
+  {0, 1073741825, "id-pe"},
+  {0, 1, "1"},
+  {"AuthorityInfoAccessSyntax", 1612709899, 0},
+  {"MAX", 1074266122, "1"},
+  {0, 2, "AccessDescription"},
+  {"AccessDescription", 1610612741, 0},
+  {"accessMethod", 1073741836, 0},
+  {"accessLocation", 2, "GeneralName"},
+  {"id-ce-cRLNumber", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "20"},
+  {"CRLNumber", 1611137027, 0},
+  {"0", 10, "MAX"},
+  {"id-ce-issuingDistributionPoint", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "28"},
+  {"IssuingDistributionPoint", 1610612741, 0},
+  {"distributionPoint", 1610637314, "DistributionPointName"},
+  {0, 4104, "0"},
+  {"onlyContainsUserCerts", 1610653700, 0},
+  {0, 1073872905, 0},
+  {0, 4104, "1"},
+  {"onlyContainsCACerts", 1610653700, 0},
+  {0, 1073872905, 0},
+  {0, 4104, "2"},
+  {"onlySomeReasons", 1610637314, "ReasonFlags"},
+  {0, 4104, "3"},
+  {"indirectCRL", 536911876, 0},
+  {0, 1073872905, 0},
+  {0, 4104, "4"},
+  {"id-ce-deltaCRLIndicator", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "27"},
+  {"BaseCRLNumber", 1073741826, "CRLNumber"},
+  {"id-ce-cRLReasons", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "21"},
+  {"CRLReason", 1610874901, 0},
+  {"unspecified", 1073741825, "0"},
+  {"keyCompromise", 1073741825, "1"},
+  {"cACompromise", 1073741825, "2"},
+  {"affiliationChanged", 1073741825, "3"},
+  {"superseded", 1073741825, "4"},
+  {"cessationOfOperation", 1073741825, "5"},
+  {"certificateHold", 1073741825, "6"},
+  {"removeFromCRL", 1, "8"},
+  {"id-ce-certificateIssuer", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "29"},
+  {"CertificateIssuer", 1073741826, "GeneralNames"},
+  {"id-ce-holdInstructionCode", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "23"},
+  {"HoldInstructionCode", 1073741836, 0},
+  {"holdInstruction", 1879048204, 0},
+  {"joint-iso-itu-t", 1073741825, "2"},
+  {"member-body", 1073741825, "2"},
+  {"us", 1073741825, "840"},
+  {"x9cm", 1073741825, "10040"},
+  {0, 1, "2"},
+  {"id-holdinstruction-none", 1879048204, 0},
+  {0, 1073741825, "holdInstruction"},
+  {0, 1, "1"},
+  {"id-holdinstruction-callissuer", 1879048204, 0},
+  {0, 1073741825, "holdInstruction"},
+  {0, 1, "2"},
+  {"id-holdinstruction-reject", 1879048204, 0},
+  {0, 1073741825, "holdInstruction"},
+  {0, 1, "3"},
+  {"id-ce-invalidityDate", 1879048204, 0},
+  {0, 1073741825, "id-ce"},
+  {0, 1, "24"},
+  {"InvalidityDate", 1082130449, 0},
+  {"VisibleString", 1610620935, 0},
+  {0, 4360, "26"},
+  {"NumericString", 1610620935, 0},
+  {0, 4360, "18"},
+  {"IA5String", 1610620935, 0},
+  {0, 4360, "22"},
+  {"TeletexString", 1610620935, 0},
+  {0, 4360, "20"},
+  {"PrintableString", 1610620935, 0},
+  {0, 4360, "19"},
+  {"UniversalString", 1610620935, 0},
+  {0, 4360, "28"},
+  {"BMPString", 1610620935, 0},
+  {0, 4360, "30"},
+  {"UTF8String", 1610620935, 0},
+  {0, 4360, "12"},
+  {"id-pkix", 1879048204, 0},
+  {"iso", 1073741825, "1"},
+  {"identified-organization", 1073741825, "3"},
+  {"dod", 1073741825, "6"},
+  {"internet", 1073741825, "1"},
+  {"security", 1073741825, "5"},
+  {"mechanisms", 1073741825, "5"},
+  {"pkix", 1, "7"},
+  {"id-pe", 1879048204, 0},
+  {0, 1073741825, "id-pkix"},
+  {0, 1, "1"},
+  {"id-qt", 1879048204, 0},
+  {0, 1073741825, "id-pkix"},
+  {0, 1, "2"},
+  {"id-kp", 1879048204, 0},
+  {0, 1073741825, "id-pkix"},
+  {0, 1, "3"},
+  {"id-ad", 1879048204, 0},
+  {0, 1073741825, "id-pkix"},
+  {0, 1, "48"},
+  {"id-qt-cps", 1879048204, 0},
+  {0, 1073741825, "id-qt"},
+  {0, 1, "1"},
+  {"id-qt-unotice", 1879048204, 0},
+  {0, 1073741825, "id-qt"},
+  {0, 1, "2"},
+  {"id-ad-ocsp", 1879048204, 0},
+  {0, 1073741825, "id-ad"},
+  {0, 1, "1"},
+  {"id-ad-caIssuers", 1879048204, 0},
+  {0, 1073741825, "id-ad"},
+  {0, 1, "2"},
+  {"Attribute", 1610612741, 0},
+  {"type", 1073741826, "AttributeType"},
+  {"values", 536870927, 0},
+  {0, 2, "AttributeValue"},
+  {"AttributeType", 1073741836, 0},
+  {"AttributeValue", 1614807053, 0},
+  {"type", 1, 0},
+  {"AttributeTypeAndValue", 1610612741, 0},
+  {"type", 1073741826, "AttributeType"},
+  {"value", 2, "AttributeValue"},
+  {"id-at", 1879048204, 0},
+  {"joint-iso-ccitt", 1073741825, "2"},
+  {"ds", 1073741825, "5"},
+  {0, 1, "4"},
+  {"id-at-initials", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "43"},
+  {"X520initials", 1073741826, "DirectoryString"},
+  {"id-at-generationQualifier", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "44"},
+  {"X520generationQualifier", 1073741826, "DirectoryString"},
+  {"id-at-surname", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "4"},
+  {"X520surName", 1073741826, "DirectoryString"},
+  {"id-at-givenName", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "42"},
+  {"X520givenName", 1073741826, "DirectoryString"},
+  {"id-at-name", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "41"},
+  {"X520name", 1073741826, "DirectoryString"},
+  {"id-at-commonName", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "3"},
+  {"X520CommonName", 1073741826, "DirectoryString"},
+  {"id-at-localityName", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "7"},
+  {"X520LocalityName", 1073741826, "DirectoryString"},
+  {"id-at-stateOrProvinceName", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "8"},
+  {"X520StateOrProvinceName", 1073741826, "DirectoryString"},
+  {"id-at-organizationName", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "10"},
+  {"X520OrganizationName", 1073741826, "DirectoryString"},
+  {"id-at-organizationalUnitName", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "11"},
+  {"X520OrganizationalUnitName", 1073741826, "DirectoryString"},
+  {"id-at-title", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "12"},
+  {"X520Title", 1073741826, "DirectoryString"},
+  {"id-at-description", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "13"},
+  {"X520Description", 1073741826, "DirectoryString"},
+  {"id-at-dnQualifier", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "46"},
+  {"X520dnQualifier", 1073741826, "PrintableString"},
+  {"id-at-countryName", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "6"},
+  {"X520countryName", 1612709890, "PrintableString"},
+  {0, 1048586, "2"},
+  {"id-at-serialNumber", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "5"},
+  {"X520serialNumber", 1073741826, "PrintableString"},
+  {"id-at-telephoneNumber", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "20"},
+  {"X520telephoneNumber", 1073741826, "PrintableString"},
+  {"id-at-facsimileTelephoneNumber", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "23"},
+  {"X520facsimileTelephoneNumber", 1073741826, "PrintableString"},
+  {"id-at-pseudonym", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "65"},
+  {"X520pseudonym", 1073741826, "DirectoryString"},
+  {"id-at-name", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "41"},
+  {"X520name", 1073741826, "DirectoryString"},
+  {"id-at-streetAddress", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "9"},
+  {"X520streetAddress", 1073741826, "DirectoryString"},
+  {"id-at-postalAddress", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-at"},
+  {0, 1, "16"},
+  {"X520postalAddress", 1073741826, "PostalAddress"},
+  {"PostalAddress", 1610612747, 0},
+  {0, 2, "DirectoryString"},
+  {"pkcs", 1879048204, 0},
+  {"iso", 1073741825, "1"},
+  {"member-body", 1073741825, "2"},
+  {"us", 1073741825, "840"},
+  {"rsadsi", 1073741825, "113549"},
+  {"pkcs", 1, "1"},
+  {"pkcs-9", 1879048204, 0},
+  {0, 1073741825, "pkcs"},
+  {0, 1, "9"},
+  {"emailAddress", 1880096780, "AttributeType"},
+  {0, 1073741825, "pkcs-9"},
+  {0, 1, "1"},
+  {"Pkcs9email", 1612709890, "IA5String"},
+  {"ub-emailaddress-length", 524298, "1"},
+  {"Name", 1610612754, 0},
+  {"rdnSequence", 2, "RDNSequence"},
+  {"RDNSequence", 1610612747, 0},
+  {0, 2, "RelativeDistinguishedName"},
+  {"DistinguishedName", 1073741826, "RDNSequence"},
+  {"RelativeDistinguishedName", 1612709903, 0},
+  {"MAX", 1074266122, "1"},
+  {0, 2, "AttributeTypeAndValue"},
+  {"Certificate", 1610612741, 0},
+  {"tbsCertificate", 1073741826, "TBSCertificate"},
+  {"signatureAlgorithm", 1073741826, "AlgorithmIdentifier"},
+  {"signature", 6, 0},
+  {"TBSCertificate", 1610612741, 0},
+  {"version", 1610653698, "Version"},
+  {0, 1073741833, "v1"},
+  {0, 2056, "0"},
+  {"serialNumber", 1073741826, "CertificateSerialNumber"},
+  {"signature", 1073741826, "AlgorithmIdentifier"},
+  {"issuer", 1073741826, "Name"},
+  {"validity", 1073741826, "Validity"},
+  {"subject", 1073741826, "Name"},
+  {"subjectPublicKeyInfo", 1073741826, "SubjectPublicKeyInfo"},
+  {"issuerUniqueID", 1610637314, "UniqueIdentifier"},
+  {0, 4104, "1"},
+  {"subjectUniqueID", 1610637314, "UniqueIdentifier"},
+  {0, 4104, "2"},
+  {"extensions", 536895490, "Extensions"},
+  {0, 2056, "3"},
+  {"Version", 1610874883, 0},
+  {"v1", 1073741825, "0"},
+  {"v2", 1073741825, "1"},
+  {"v3", 1, "2"},
+  {"CertificateSerialNumber", 1073741827, 0},
+  {"Validity", 1610612741, 0},
+  {"notBefore", 1073741826, "Time"},
+  {"notAfter", 2, "Time"},
+  {"Time", 1610612754, 0},
+  {"utcTime", 1090519057, 0},
+  {"generalTime", 8388625, 0},
+  {"UniqueIdentifier", 1073741830, 0},
+  {"SubjectPublicKeyInfo", 1610612741, 0},
+  {"algorithm", 1073741826, "AlgorithmIdentifier"},
+  {"subjectPublicKey", 6, 0},
+  {"Extensions", 1612709899, 0},
+  {"MAX", 1074266122, "1"},
+  {0, 2, "Extension"},
+  {"Extension", 1610612741, 0},
+  {"extnID", 1073741836, 0},
+  {"critical", 1610645508, 0},
+  {0, 131081, 0},
+  {"extnValue", 7, 0},
+  {"CertificateList", 1610612741, 0},
+  {"tbsCertList", 1073741826, "TBSCertList"},
+  {"signatureAlgorithm", 1073741826, "AlgorithmIdentifier"},
+  {"signature", 6, 0},
+  {"TBSCertList", 1610612741, 0},
+  {"version", 1073758210, "Version"},
+  {"signature", 1073741826, "AlgorithmIdentifier"},
+  {"issuer", 1073741826, "Name"},
+  {"thisUpdate", 1073741826, "Time"},
+  {"nextUpdate", 1073758210, "Time"},
+  {"revokedCertificates", 1610629131, 0},
+  {0, 536870917, 0},
+  {"userCertificate", 1073741826, "CertificateSerialNumber"},
+  {"revocationDate", 1073741826, "Time"},
+  {"crlEntryExtensions", 16386, "Extensions"},
+  {"crlExtensions", 536895490, "Extensions"},
+  {0, 2056, "0"},
+  {"AlgorithmIdentifier", 1610612741, 0},
+  {"algorithm", 1073741836, 0},
+  {"parameters", 541081613, 0},
+  {"algorithm", 1, 0},
+  {"pkcs-1", 1879048204, 0},
+  {0, 1073741825, "pkcs"},
+  {0, 1, "1"},
+  {"rsaEncryption", 1879048204, 0},
+  {0, 1073741825, "pkcs-1"},
+  {0, 1, "1"},
+  {"md2WithRSAEncryption", 1879048204, 0},
+  {0, 1073741825, "pkcs-1"},
+  {0, 1, "2"},
+  {"md5WithRSAEncryption", 1879048204, 0},
+  {0, 1073741825, "pkcs-1"},
+  {0, 1, "4"},
+  {"sha1WithRSAEncryption", 1879048204, 0},
+  {0, 1073741825, "pkcs-1"},
+  {0, 1, "5"},
+  {"id-dsa-with-sha1", 1879048204, 0},
+  {"iso", 1073741825, "1"},
+  {"member-body", 1073741825, "2"},
+  {"us", 1073741825, "840"},
+  {"x9-57", 1073741825, "10040"},
+  {"x9algorithm", 1073741825, "4"},
+  {0, 1, "3"},
+  {"Dss-Sig-Value", 1610612741, 0},
+  {"r", 1073741827, 0},
+  {"s", 3, 0},
+  {"dhpublicnumber", 1879048204, 0},
+  {"iso", 1073741825, "1"},
+  {"member-body", 1073741825, "2"},
+  {"us", 1073741825, "840"},
+  {"ansi-x942", 1073741825, "10046"},
+  {"number-type", 1073741825, "2"},
+  {0, 1, "1"},
+  {"DomainParameters", 1610612741, 0},
+  {"p", 1073741827, 0},
+  {"g", 1073741827, 0},
+  {"q", 1073741827, 0},
+  {"j", 1073758211, 0},
+  {"validationParms", 16386, "ValidationParms"},
+  {"ValidationParms", 1610612741, 0},
+  {"seed", 1073741830, 0},
+  {"pgenCounter", 3, 0},
+  {"id-dsa", 1879048204, 0},
+  {"iso", 1073741825, "1"},
+  {"member-body", 1073741825, "2"},
+  {"us", 1073741825, "840"},
+  {"x9-57", 1073741825, "10040"},
+  {"x9algorithm", 1073741825, "4"},
+  {0, 1, "1"},
+  {"Dss-Parms", 1610612741, 0},
+  {"p", 1073741827, 0},
+  {"q", 1073741827, 0},
+  {"g", 3, 0},
+  {"ORAddress", 1610612741, 0},
+  {"built-in-standard-attributes", 1073741826, "BuiltInStandardAttributes"},
+  {"built-in-domain-defined-attributes", 1073758210,
+   "BuiltInDomainDefinedAttributes"},
+  {"extension-attributes", 16386, "ExtensionAttributes"},
+  {"BuiltInStandardAttributes", 1610612741, 0},
+  {"country-name", 1073758210, "CountryName"},
+  {"administration-domain-name", 1073758210, "AdministrationDomainName"},
+  {"network-address", 1610637314, "NetworkAddress"},
+  {0, 2056, "0"},
+  {"terminal-identifier", 1610637314, "TerminalIdentifier"},
+  {0, 2056, "1"},
+  {"private-domain-name", 1610637314, "PrivateDomainName"},
+  {0, 2056, "2"},
+  {"organization-name", 1610637314, "OrganizationName"},
+  {0, 2056, "3"},
+  {"numeric-user-identifier", 1610637314, "NumericUserIdentifier"},
+  {0, 2056, "4"},
+  {"personal-name", 1610637314, "PersonalName"},
+  {0, 2056, "5"},
+  {"organizational-unit-names", 536895490, "OrganizationalUnitNames"},
+  {0, 2056, "6"},
+  {"CountryName", 1610620946, 0},
+  {0, 1073746952, "1"},
+  {"x121-dcc-code", 1612709890, "NumericString"},
+  {0, 1048586, "ub-country-name-numeric-length"},
+  {"iso-3166-alpha2-code", 538968066, "PrintableString"},
+  {0, 1048586, "ub-country-name-alpha-length"},
+  {"AdministrationDomainName", 1610620946, 0},
+  {0, 1073744904, "2"},
+  {"numeric", 1612709890, "NumericString"},
+  {"ub-domain-name-length", 524298, "0"},
+  {"printable", 538968066, "PrintableString"},
+  {"ub-domain-name-length", 524298, "0"},
+  {"NetworkAddress", 1073741826, "X121Address"},
+  {"X121Address", 1612709890, "NumericString"},
+  {"ub-x121-address-length", 524298, "1"},
+  {"TerminalIdentifier", 1612709890, "PrintableString"},
+  {"ub-terminal-id-length", 524298, "1"},
+  {"PrivateDomainName", 1610612754, 0},
+  {"numeric", 1612709890, "NumericString"},
+  {"ub-domain-name-length", 524298, "1"},
+  {"printable", 538968066, "PrintableString"},
+  {"ub-domain-name-length", 524298, "1"},
+  {"OrganizationName", 1612709890, "PrintableString"},
+  {"ub-organization-name-length", 524298, "1"},
+  {"NumericUserIdentifier", 1612709890, "NumericString"},
+  {"ub-numeric-user-id-length", 524298, "1"},
+  {"PersonalName", 1610612750, 0},
+  {"surname", 1814044674, "PrintableString"},
+  {0, 1073745928, "0"},
+  {"ub-surname-length", 524298, "1"},
+  {"given-name", 1814061058, "PrintableString"},
+  {0, 1073745928, "1"},
+  {"ub-given-name-length", 524298, "1"},
+  {"initials", 1814061058, "PrintableString"},
+  {0, 1073745928, "2"},
+  {"ub-initials-length", 524298, "1"},
+  {"generation-qualifier", 740319234, "PrintableString"},
+  {0, 1073745928, "3"},
+  {"ub-generation-qualifier-length", 524298, "1"},
+  {"OrganizationalUnitNames", 1612709899, 0},
+  {"ub-organizational-units", 1074266122, "1"},
+  {0, 2, "OrganizationalUnitName"},
+  {"OrganizationalUnitName", 1612709890, "PrintableString"},
+  {"ub-organizational-unit-name-length", 524298, "1"},
+  {"BuiltInDomainDefinedAttributes", 1612709899, 0},
+  {"ub-domain-defined-attributes", 1074266122, "1"},
+  {0, 2, "BuiltInDomainDefinedAttribute"},
+  {"BuiltInDomainDefinedAttribute", 1610612741, 0},
+  {"type", 1612709890, "PrintableString"},
+  {"ub-domain-defined-attribute-type-length", 524298, "1"},
+  {"value", 538968066, "PrintableString"},
+  {"ub-domain-defined-attribute-value-length", 524298, "1"},
+  {"ExtensionAttributes", 1612709903, 0},
+  {"ub-extension-attributes", 1074266122, "1"},
+  {0, 2, "ExtensionAttribute"},
+  {"ExtensionAttribute", 1610612741, 0},
+  {"extension-attribute-type", 1611145219, 0},
+  {0, 1073743880, "0"},
+  {"0", 10, "ub-extension-attributes"},
+  {"extension-attribute-value", 541073421, 0},
+  {0, 1073743880, "1"},
+  {"extension-attribute-type", 1, 0},
+  {"common-name", 1342177283, "1"},
+  {"CommonName", 1612709890, "PrintableString"},
+  {"ub-common-name-length", 524298, "1"},
+  {"teletex-common-name", 1342177283, "2"},
+  {"TeletexCommonName", 1612709890, "TeletexString"},
+  {"ub-common-name-length", 524298, "1"},
+  {"teletex-organization-name", 1342177283, "3"},
+  {"TeletexOrganizationName", 1612709890, "TeletexString"},
+  {"ub-organization-name-length", 524298, "1"},
+  {"teletex-personal-name", 1342177283, "4"},
+  {"TeletexPersonalName", 1610612750, 0},
+  {"surname", 1814044674, "TeletexString"},
+  {0, 1073743880, "0"},
+  {"ub-surname-length", 524298, "1"},
+  {"given-name", 1814061058, "TeletexString"},
+  {0, 1073743880, "1"},
+  {"ub-given-name-length", 524298, "1"},
+  {"initials", 1814061058, "TeletexString"},
+  {0, 1073743880, "2"},
+  {"ub-initials-length", 524298, "1"},
+  {"generation-qualifier", 740319234, "TeletexString"},
+  {0, 1073743880, "3"},
+  {"ub-generation-qualifier-length", 524298, "1"},
+  {"teletex-organizational-unit-names", 1342177283, "5"},
+  {"TeletexOrganizationalUnitNames", 1612709899, 0},
+  {"ub-organizational-units", 1074266122, "1"},
+  {0, 2, "TeletexOrganizationalUnitName"},
+  {"TeletexOrganizationalUnitName", 1612709890, "TeletexString"},
+  {"ub-organizational-unit-name-length", 524298, "1"},
+  {"pds-name", 1342177283, "7"},
+  {"PDSName", 1612709890, "PrintableString"},
+  {"ub-pds-name-length", 524298, "1"},
+  {"physical-delivery-country-name", 1342177283, "8"},
+  {"PhysicalDeliveryCountryName", 1610612754, 0},
+  {"x121-dcc-code", 1612709890, "NumericString"},
+  {0, 1048586, "ub-country-name-numeric-length"},
+  {"iso-3166-alpha2-code", 538968066, "PrintableString"},
+  {0, 1048586, "ub-country-name-alpha-length"},
+  {"postal-code", 1342177283, "9"},
+  {"PostalCode", 1610612754, 0},
+  {"numeric-code", 1612709890, "NumericString"},
+  {"ub-postal-code-length", 524298, "1"},
+  {"printable-code", 538968066, "PrintableString"},
+  {"ub-postal-code-length", 524298, "1"},
+  {"physical-delivery-office-name", 1342177283, "10"},
+  {"PhysicalDeliveryOfficeName", 1073741826, "PDSParameter"},
+  {"physical-delivery-office-number", 1342177283, "11"},
+  {"PhysicalDeliveryOfficeNumber", 1073741826, "PDSParameter"},
+  {"extension-OR-address-components", 1342177283, "12"},
+  {"ExtensionORAddressComponents", 1073741826, "PDSParameter"},
+  {"physical-delivery-personal-name", 1342177283, "13"},
+  {"PhysicalDeliveryPersonalName", 1073741826, "PDSParameter"},
+  {"physical-delivery-organization-name", 1342177283, "14"},
+  {"PhysicalDeliveryOrganizationName", 1073741826, "PDSParameter"},
+  {"extension-physical-delivery-address-components", 1342177283, "15"},
+  {"ExtensionPhysicalDeliveryAddressComponents", 1073741826, "PDSParameter"},
+  {"unformatted-postal-address", 1342177283, "16"},
+  {"UnformattedPostalAddress", 1610612750, 0},
+  {"printable-address", 1814052875, 0},
+  {"ub-pds-physical-address-lines", 1074266122, "1"},
+  {0, 538968066, "PrintableString"},
+  {"ub-pds-parameter-length", 524298, "1"},
+  {"teletex-string", 740311042, "TeletexString"},
+  {"ub-unformatted-address-length", 524298, "1"},
+  {"street-address", 1342177283, "17"},
+  {"StreetAddress", 1073741826, "PDSParameter"},
+  {"post-office-box-address", 1342177283, "18"},
+  {"PostOfficeBoxAddress", 1073741826, "PDSParameter"},
+  {"poste-restante-address", 1342177283, "19"},
+  {"PosteRestanteAddress", 1073741826, "PDSParameter"},
+  {"unique-postal-name", 1342177283, "20"},
+  {"UniquePostalName", 1073741826, "PDSParameter"},
+  {"local-postal-attributes", 1342177283, "21"},
+  {"LocalPostalAttributes", 1073741826, "PDSParameter"},
+  {"PDSParameter", 1610612750, 0},
+  {"printable-string", 1814052866, "PrintableString"},
+  {"ub-pds-parameter-length", 524298, "1"},
+  {"teletex-string", 740311042, "TeletexString"},
+  {"ub-pds-parameter-length", 524298, "1"},
+  {"extended-network-address", 1342177283, "22"},
+  {"ExtendedNetworkAddress", 1610612754, 0},
+  {"e163-4-address", 1610612741, 0},
+  {"number", 1612718082, "NumericString"},
+  {0, 1073743880, "0"},
+  {"ub-e163-4-number-length", 524298, "1"},
+  {"sub-address", 538992642, "NumericString"},
+  {0, 1073743880, "1"},
+  {"ub-e163-4-sub-address-length", 524298, "1"},
+  {"psap-address", 536879106, "PresentationAddress"},
+  {0, 2056, "0"},
+  {"PresentationAddress", 1610612741, 0},
+  {"pSelector", 1610637319, 0},
+  {0, 2056, "0"},
+  {"sSelector", 1610637319, 0},
+  {0, 2056, "1"},
+  {"tSelector", 1610637319, 0},
+  {0, 2056, "2"},
+  {"nAddresses", 538976271, 0},
+  {0, 1073743880, "3"},
+  {"MAX", 1074266122, "1"},
+  {0, 7, 0},
+  {"terminal-type", 1342177283, "23"},
+  {"TerminalType", 1610874883, 0},
+  {"telex", 1073741825, "3"},
+  {"teletex", 1073741825, "4"},
+  {"g3-facsimile", 1073741825, "5"},
+  {"g4-facsimile", 1073741825, "6"},
+  {"ia5-terminal", 1073741825, "7"},
+  {"videotex", 1, "8"},
+  {"teletex-domain-defined-attributes", 1342177283, "6"},
+  {"TeletexDomainDefinedAttributes", 1612709899, 0},
+  {"ub-domain-defined-attributes", 1074266122, "1"},
+  {0, 2, "TeletexDomainDefinedAttribute"},
+  {"TeletexDomainDefinedAttribute", 1610612741, 0},
+  {"type", 1612709890, "TeletexString"},
+  {"ub-domain-defined-attribute-type-length", 524298, "1"},
+  {"value", 538968066, "TeletexString"},
+  {"ub-domain-defined-attribute-value-length", 524298, "1"},
+  {"ub-name", 1342177283, "32768"},
+  {"ub-common-name", 1342177283, "64"},
+  {"ub-locality-name", 1342177283, "128"},
+  {"ub-state-name", 1342177283, "128"},
+  {"ub-organization-name", 1342177283, "64"},
+  {"ub-organizational-unit-name", 1342177283, "64"},
+  {"ub-title", 1342177283, "64"},
+  {"ub-match", 1342177283, "128"},
+  {"ub-emailaddress-length", 1342177283, "128"},
+  {"ub-common-name-length", 1342177283, "64"},
+  {"ub-country-name-alpha-length", 1342177283, "2"},
+  {"ub-country-name-numeric-length", 1342177283, "3"},
+  {"ub-domain-defined-attributes", 1342177283, "4"},
+  {"ub-domain-defined-attribute-type-length", 1342177283, "8"},
+  {"ub-domain-defined-attribute-value-length", 1342177283, "128"},
+  {"ub-domain-name-length", 1342177283, "16"},
+  {"ub-extension-attributes", 1342177283, "256"},
+  {"ub-e163-4-number-length", 1342177283, "15"},
+  {"ub-e163-4-sub-address-length", 1342177283, "40"},
+  {"ub-generation-qualifier-length", 1342177283, "3"},
+  {"ub-given-name-length", 1342177283, "16"},
+  {"ub-initials-length", 1342177283, "5"},
+  {"ub-integer-options", 1342177283, "256"},
+  {"ub-numeric-user-id-length", 1342177283, "32"},
+  {"ub-organization-name-length", 1342177283, "64"},
+  {"ub-organizational-unit-name-length", 1342177283, "32"},
+  {"ub-organizational-units", 1342177283, "4"},
+  {"ub-pds-name-length", 1342177283, "16"},
+  {"ub-pds-parameter-length", 1342177283, "30"},
+  {"ub-pds-physical-address-lines", 1342177283, "6"},
+  {"ub-postal-code-length", 1342177283, "16"},
+  {"ub-surname-length", 1342177283, "40"},
+  {"ub-terminal-id-length", 1342177283, "24"},
+  {"ub-unformatted-address-length", 1342177283, "180"},
+  {"ub-x121-address-length", 1342177283, "16"},
+  {"pkcs-7-ContentInfo", 1610612741, 0},
+  {"contentType", 1073741826, "pkcs-7-ContentType"},
+  {"content", 541073421, 0},
+  {0, 1073743880, "0"},
+  {"contentType", 1, 0},
+  {"pkcs-7-DigestInfo", 1610612741, 0},
+  {"digestAlgorithm", 1073741826, "pkcs-7-DigestAlgorithmIdentifier"},
+  {"digest", 2, "pkcs-7-Digest"},
+  {"pkcs-7-Digest", 1073741831, 0},
+  {"pkcs-7-ContentType", 1073741836, 0},
+  {"pkcs-7-SignedData", 1610612741, 0},
+  {"version", 1073741826, "pkcs-7-CMSVersion"},
+  {"digestAlgorithms", 1073741826, "pkcs-7-DigestAlgorithmIdentifiers"},
+  {"encapContentInfo", 1073741826, "pkcs-7-EncapsulatedContentInfo"},
+  {"certificates", 1610637314, "pkcs-7-CertificateSet"},
+  {0, 4104, "0"},
+  {"crls", 1610637314, "pkcs-7-CertificateRevocationLists"},
+  {0, 4104, "1"},
+  {"signerInfos", 2, "pkcs-7-SignerInfos"},
+  {"pkcs-7-CMSVersion", 1610874883, 0},
+  {"v0", 1073741825, "0"},
+  {"v1", 1073741825, "1"},
+  {"v2", 1073741825, "2"},
+  {"v3", 1073741825, "3"},
+  {"v4", 1, "4"},
+  {"pkcs-7-DigestAlgorithmIdentifiers", 1610612751, 0},
+  {0, 2, "pkcs-7-DigestAlgorithmIdentifier"},
+  {"pkcs-7-DigestAlgorithmIdentifier", 1073741826, "AlgorithmIdentifier"},
+  {"pkcs-7-EncapsulatedContentInfo", 1610612741, 0},
+  {"eContentType", 1073741826, "pkcs-7-ContentType"},
+  {"eContent", 536895495, 0},
+  {0, 2056, "0"},
+  {"pkcs-7-CertificateRevocationLists", 1610612751, 0},
+  {0, 13, 0},
+  {"pkcs-7-CertificateChoices", 1610612754, 0},
+  {"certificate", 13, 0},
+  {"pkcs-7-CertificateSet", 1610612751, 0},
+  {0, 2, "pkcs-7-CertificateChoices"},
+  {"pkcs-7-SignerInfos", 1610612751, 0},
+  {0, 13, 0},
+  {"pkcs-10-CertificationRequestInfo", 1610612741, 0},
+  {"version", 1610874883, 0},
+  {"v1", 1, "0"},
+  {"subject", 1073741826, "Name"},
+  {"subjectPKInfo", 1073741826, "SubjectPublicKeyInfo"},
+  {"attributes", 536879106, "Attributes"},
+  {0, 4104, "0"},
+  {"Attributes", 1610612751, 0},
+  {0, 2, "Attribute"},
+  {"pkcs-10-CertificationRequest", 1610612741, 0},
+  {"certificationRequestInfo", 1073741826, "pkcs-10-CertificationRequestInfo"},
+  {"signatureAlgorithm", 1073741826, "AlgorithmIdentifier"},
+  {"signature", 6, 0},
+  {"pkcs-9-ub-challengePassword", 1342177283, "255"},
+  {"pkcs-9-certTypes", 1879048204, 0},
+  {0, 1073741825, "pkcs-9"},
+  {0, 1, "22"},
+  {"pkcs-9-crlTypes", 1879048204, 0},
+  {0, 1073741825, "pkcs-9"},
+  {0, 1, "23"},
+  {"pkcs-9-at-challengePassword", 1879048204, 0},
+  {0, 1073741825, "pkcs-9"},
+  {0, 1, "7"},
+  {"pkcs-9-challengePassword", 1610612754, 0},
+  {"printableString", 1612709890, "PrintableString"},
+  {"pkcs-9-ub-challengePassword", 524298, "1"},
+  {"utf8String", 538968066, "UTF8String"},
+  {"pkcs-9-ub-challengePassword", 524298, "1"},
+  {"pkcs-9-at-localKeyId", 1879048204, 0},
+  {0, 1073741825, "pkcs-9"},
+  {0, 1, "21"},
+  {"pkcs-9-localKeyId", 1073741831, 0},
+  {"pkcs-9-at-friendlyName", 1879048204, 0},
+  {0, 1073741825, "pkcs-9"},
+  {0, 1, "20"},
+  {"pkcs-9-friendlyName", 1612709890, "BMPString"},
+  {"255", 524298, "1"},
+  {"pkcs-8-PrivateKeyInfo", 1610612741, 0},
+  {"version", 1073741826, "pkcs-8-Version"},
+  {"privateKeyAlgorithm", 1073741826, "AlgorithmIdentifier"},
+  {"privateKey", 1073741826, "pkcs-8-PrivateKey"},
+  {"attributes", 536895490, "Attributes"},
+  {0, 4104, "0"},
+  {"pkcs-8-Version", 1610874883, 0},
+  {"v1", 1, "0"},
+  {"pkcs-8-PrivateKey", 1073741831, 0},
+  {"pkcs-8-Attributes", 1610612751, 0},
+  {0, 2, "Attribute"},
+  {"pkcs-8-EncryptedPrivateKeyInfo", 1610612741, 0},
+  {"encryptionAlgorithm", 1073741826, "AlgorithmIdentifier"},
+  {"encryptedData", 2, "pkcs-8-EncryptedData"},
+  {"pkcs-8-EncryptedData", 1073741831, 0},
+  {"pkcs-5", 1879048204, 0},
+  {0, 1073741825, "pkcs"},
+  {0, 1, "5"},
+  {"pkcs-5-encryptionAlgorithm", 1879048204, 0},
+  {"iso", 1073741825, "1"},
+  {"member-body", 1073741825, "2"},
+  {"us", 1073741825, "840"},
+  {"rsadsi", 1073741825, "113549"},
+  {0, 1, "3"},
+  {"pkcs-5-des-EDE3-CBC", 1879048204, 0},
+  {0, 1073741825, "pkcs-5-encryptionAlgorithm"},
+  {0, 1, "7"},
+  {"pkcs-5-des-EDE3-CBC-params", 1612709895, 0},
+  {0, 1048586, "8"},
+  {"pkcs-5-id-PBES2", 1879048204, 0},
+  {0, 1073741825, "pkcs-5"},
+  {0, 1, "13"},
+  {"pkcs-5-PBES2-params", 1610612741, 0},
+  {"keyDerivationFunc", 1073741826, "AlgorithmIdentifier"},
+  {"encryptionScheme", 2, "AlgorithmIdentifier"},
+  {"pkcs-5-id-PBKDF2", 1879048204, 0},
+  {0, 1073741825, "pkcs-5"},
+  {0, 1, "12"},
+  {"pkcs-5-PBKDF2-params", 1610612741, 0},
+  {"salt", 1610612754, 0},
+  {"specified", 1073741831, 0},
+  {"otherSource", 2, "AlgorithmIdentifier"},
+  {"iterationCount", 1611137027, 0},
+  {"1", 10, "MAX"},
+  {"keyLength", 1611153411, 0},
+  {"1", 10, "MAX"},
+  {"prf", 16386, "AlgorithmIdentifier"},
+  {"pkcs-12", 1879048204, 0},
+  {0, 1073741825, "pkcs"},
+  {0, 1, "12"},
+  {"pkcs-12-PFX", 1610612741, 0},
+  {"version", 1610874883, 0},
+  {"v3", 1, "3"},
+  {"authSafe", 1073741826, "pkcs-7-ContentInfo"},
+  {"macData", 16386, "pkcs-12-MacData"},
+  {"pkcs-12-PbeParams", 1610612741, 0},
+  {"salt", 1073741831, 0},
+  {"iterations", 3, 0},
+  {"pkcs-12-MacData", 1610612741, 0},
+  {"mac", 1073741826, "pkcs-7-DigestInfo"},
+  {"macSalt", 1073741831, 0},
+  {"iterations", 536903683, 0},
+  {0, 9, "1"},
+  {"pkcs-12-AuthenticatedSafe", 1610612747, 0},
+  {0, 2, "pkcs-7-ContentInfo"},
+  {"pkcs-12-SafeContents", 1610612747, 0},
+  {0, 2, "pkcs-12-SafeBag"},
+  {"pkcs-12-SafeBag", 1610612741, 0},
+  {"bagId", 1073741836, 0},
+  {"bagValue", 1614815245, 0},
+  {0, 1073743880, "0"},
+  {"badId", 1, 0},
+  {"bagAttributes", 536887311, 0},
+  {0, 2, "pkcs-12-PKCS12Attribute"},
+  {"pkcs-12-bagtypes", 1879048204, 0},
+  {0, 1073741825, "pkcs-12"},
+  {0, 1073741825, "10"},
+  {0, 1, "1"},
+  {"pkcs-12-keyBag", 1879048204, 0},
+  {0, 1073741825, "pkcs-12-bagtypes"},
+  {0, 1, "1"},
+  {"pkcs-12-pkcs8ShroudedKeyBag", 1879048204, 0},
+  {0, 1073741825, "pkcs-12-bagtypes"},
+  {0, 1, "2"},
+  {"pkcs-12-certBag", 1879048204, 0},
+  {0, 1073741825, "pkcs-12-bagtypes"},
+  {0, 1, "3"},
+  {"pkcs-12-crlBag", 1879048204, 0},
+  {0, 1073741825, "pkcs-12-bagtypes"},
+  {0, 1, "4"},
+  {"pkcs-12-KeyBag", 1073741826, "pkcs-8-PrivateKeyInfo"},
+  {"pkcs-12-PKCS8ShroudedKeyBag", 1073741826, "pkcs-8-EncryptedPrivateKeyInfo"},
+  {"pkcs-12-CertBag", 1610612741, 0},
+  {"certId", 1073741836, 0},
+  {"certValue", 541073421, 0},
+  {0, 1073743880, "0"},
+  {"certId", 1, 0},
+  {"pkcs-12-CRLBag", 1610612741, 0},
+  {"crlId", 1073741836, 0},
+  {"crlValue", 541073421, 0},
+  {0, 1073743880, "0"},
+  {"crlId", 1, 0},
+  {"pkcs-12-PKCS12Attribute", 1073741826, "Attribute"},
+  {"pkcs-7-data", 1879048204, 0},
+  {"iso", 1073741825, "1"},
+  {"member-body", 1073741825, "2"},
+  {"us", 1073741825, "840"},
+  {"rsadsi", 1073741825, "113549"},
+  {"pkcs", 1073741825, "1"},
+  {"pkcs7", 1073741825, "7"},
+  {0, 1, "1"},
+  {"pkcs-7-encryptedData", 1879048204, 0},
+  {"iso", 1073741825, "1"},
+  {"member-body", 1073741825, "2"},
+  {"us", 1073741825, "840"},
+  {"rsadsi", 1073741825, "113549"},
+  {"pkcs", 1073741825, "1"},
+  {"pkcs7", 1073741825, "7"},
+  {0, 1, "6"},
+  {"pkcs-7-Data", 1073741831, 0},
+  {"pkcs-7-EncryptedData", 1610612741, 0},
+  {"version", 1073741826, "pkcs-7-CMSVersion"},
+  {"encryptedContentInfo", 1073741826, "pkcs-7-EncryptedContentInfo"},
+  {"unprotectedAttrs", 536895490, "pkcs-7-UnprotectedAttributes"},
+  {0, 4104, "1"},
+  {"pkcs-7-EncryptedContentInfo", 1610612741, 0},
+  {"contentType", 1073741826, "pkcs-7-ContentType"},
+  {"contentEncryptionAlgorithm", 1073741826,
+   "pkcs-7-ContentEncryptionAlgorithmIdentifier"},
+  {"encryptedContent", 536895490, "pkcs-7-EncryptedContent"},
+  {0, 4104, "0"},
+  {"pkcs-7-ContentEncryptionAlgorithmIdentifier", 1073741826,
+   "AlgorithmIdentifier"},
+  {"pkcs-7-EncryptedContent", 1073741831, 0},
+  {"pkcs-7-UnprotectedAttributes", 1612709903, 0},
+  {"MAX", 1074266122, "1"},
+  {0, 2, "Attribute"},
+  {"id-at-ldap-DC", 1880096780, "AttributeType"},
+  {0, 1073741825, "0"},
+  {0, 1073741825, "9"},
+  {0, 1073741825, "2342"},
+  {0, 1073741825, "19200300"},
+  {0, 1073741825, "100"},
+  {0, 1073741825, "1"},
+  {0, 1, "25"},
+  {"ldap-DC", 1073741826, "IA5String"},
+  {"id-at-ldap-UID", 1880096780, "AttributeType"},
+  {0, 1073741825, "0"},
+  {0, 1073741825, "9"},
+  {0, 1073741825, "2342"},
+  {0, 1073741825, "19200300"},
+  {0, 1073741825, "100"},
+  {0, 1073741825, "1"},
+  {0, 1, "1"},
+  {"ldap-UID", 1073741826, "DirectoryString"},
+  {"id-pda", 1879048204, 0},
+  {0, 1073741825, "id-pkix"},
+  {0, 1, "9"},
+  {"id-pda-dateOfBirth", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-pda"},
+  {0, 1, "1"},
+  {"DateOfBirth", 1082130449, 0},
+  {"id-pda-placeOfBirth", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-pda"},
+  {0, 1, "2"},
+  {"PlaceOfBirth", 1073741826, "DirectoryString"},
+  {"id-pda-gender", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-pda"},
+  {0, 1, "3"},
+  {"Gender", 1612709890, "PrintableString"},
+  {0, 1048586, "1"},
+  {"id-pda-countryOfCitizenship", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-pda"},
+  {0, 1, "4"},
+  {"CountryOfCitizenship", 1612709890, "PrintableString"},
+  {0, 1048586, "2"},
+  {"id-pda-countryOfResidence", 1880096780, "AttributeType"},
+  {0, 1073741825, "id-pda"},
+  {0, 1, "5"},
+  {"CountryOfResidence", 538968066, "PrintableString"},
+  {0, 1048586, "2"},
+  {0, 0, 0}
+};
diff --git a/tests/test-crypto-tlscreds.c b/tests/test-crypto-tlscreds.c
new file mode 100644
index 0000000..cf82764
--- /dev/null
+++ b/tests/test-crypto-tlscreds.c
@@ -0,0 +1,727 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daniel P. Berrange <berrange@redhat.com>
+ */
+
+
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+
+#include "config-host.h"
+#include "crypto-tls-helpers.h"
+#include "crypto/tlscreds.h"
+
+#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
+
+#define WORKDIR "tests/test-crypto-tlscreds-work/"
+#define KEYFILE WORKDIR "key-ctx.pem"
+
+struct QCryptoTLSCredsTestData {
+    bool isServer;
+    const char *cacrt;
+    const char *crt;
+    bool expectFail;
+};
+
+
+static QCryptoTLSCreds *test_tls_creds_create(QCryptoTLSCredsEndpoint endpoint,
+                                              const char *certdir,
+                                              Error **errp)
+{
+    Object *creds = object_new_propv(
+        TYPE_QCRYPTO_TLS_CREDS,
+        "testtlscreds",
+        errp,
+        "credtype", "x509",
+        "endpoint", (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
+                     "server" : "client"),
+        "dir", certdir,
+        "verify-peer", "yes",
+        "sanity-check", "yes",
+        NULL);
+
+    if (*errp) {
+        return NULL;
+    }
+    return QCRYPTO_TLS_CREDS(creds);
+}
+
+/*
+ * This tests sanity checking of our own certificates
+ *
+ * The code being tested is used when TLS creds are created,
+ * and aim to ensure QMEU has been configured with sane
+ * certificates. This allows us to give much much much
+ * clearer error messages to the admin when they misconfigure
+ * things.
+ */
+static void test_tls_creds(const void *opaque)
+{
+    struct QCryptoTLSCredsTestData *data =
+        (struct QCryptoTLSCredsTestData *)opaque;
+    QCryptoTLSCreds *creds;
+    Error *err = NULL;
+
+#define CERT_DIR "tests/test-crypto-tlscreds-certs/"
+    mkdir(CERT_DIR, 0700);
+
+    unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
+    if (data->isServer) {
+        unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
+        unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
+    } else {
+        unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
+        unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
+    }
+
+    if (access(data->cacrt, R_OK) == 0) {
+        g_assert(link(data->cacrt,
+                      CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT) == 0);
+    }
+    if (data->isServer) {
+        if (access(data->crt, R_OK) == 0) {
+            g_assert(link(data->crt,
+                          CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT) == 0);
+        }
+        g_assert(link(KEYFILE,
+                      CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY) == 0);
+    } else {
+        if (access(data->crt, R_OK) == 0) {
+            g_assert(link(data->crt,
+                          CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT) == 0);
+        }
+        g_assert(link(KEYFILE,
+                      CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY) == 0);
+    }
+
+    creds = test_tls_creds_create(
+        (data->isServer ?
+         QCRYPTO_TLS_CREDS_ENDPOINT_SERVER :
+         QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT),
+        CERT_DIR,
+        &err);
+
+    if (data->expectFail) {
+        error_free(err);
+        g_assert(creds == NULL);
+    } else {
+        if (err) {
+            g_printerr("Failed to generate creds: %s\n",
+                       error_get_pretty(err));
+            error_free(err);
+        }
+        g_assert(creds != NULL);
+    }
+
+    unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
+    if (data->isServer) {
+        unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
+        unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
+    } else {
+        unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
+        unlink(CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
+    }
+    rmdir(CERT_DIR);
+    object_unref(OBJECT(creds));
+    object_property_del(
+        container_get(object_get_root(), "/objects"),
+        "testtlscreds", NULL);
+}
+
+
+
+int main(int argc, char **argv)
+{
+    int ret;
+
+    module_call_init(MODULE_INIT_QOM);
+    g_test_init(&argc, &argv, NULL);
+    qcrypto_tls_creds_dummy();
+    setenv("GNUTLS_FORCE_FIPS_MODE", "2", 1);
+
+    mkdir(WORKDIR, 0700);
+
+    test_tls_init(KEYFILE);
+
+# define TLS_TEST_REG(name, isServer, caCrt, crt, expectFail)           \
+    struct QCryptoTLSCredsTestData name = {                             \
+        isServer, caCrt, crt, expectFail                                \
+    };                                                                  \
+    g_test_add_data_func("/qcrypto/tlscreds/" # name,                   \
+                         &name, test_tls_creds);                        \
+
+    /* A perfect CA, perfect client & perfect server */
+
+    /* Basic:CA:critical */
+    TLS_ROOT_REQ(cacertreq,
+                 "UK", "qemu CA", NULL, NULL, NULL, NULL,
+                 true, true, true,
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
+                 false, false, NULL, NULL,
+                 0, 0);
+
+    TLS_CERT_REQ(servercertreq, cacertreq,
+                 "UK", "qemu.org", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+                 0, 0);
+    TLS_CERT_REQ(clientcertreq, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
+                 0, 0);
+
+    TLS_TEST_REG(perfectserver, true,
+                 cacertreq.filename, servercertreq.filename, false);
+    TLS_TEST_REG(perfectclient, false,
+                 cacertreq.filename, clientcertreq.filename, false);
+
+
+    /* Some other CAs which are good */
+
+    /* Basic:CA:critical */
+    TLS_ROOT_REQ(cacert1req,
+                 "UK", "qemu CA 1", NULL, NULL, NULL, NULL,
+                 true, true, true,
+                 false, false, 0,
+                 false, false, NULL, NULL,
+                 0, 0);
+    TLS_CERT_REQ(servercert1req, cacert1req,
+                 "UK", "qemu.org", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+                 0, 0);
+
+    /* Basic:CA:not-critical */
+    TLS_ROOT_REQ(cacert2req,
+                 "UK", "qemu CA 2", NULL, NULL, NULL, NULL,
+                 true, false, true,
+                 false, false, 0,
+                 false, false, NULL, NULL,
+                 0, 0);
+    TLS_CERT_REQ(servercert2req, cacert2req,
+                 "UK", "qemu.org", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+                 0, 0);
+
+    /* Key usage:cert-sign:critical */
+    TLS_ROOT_REQ(cacert3req,
+                 "UK", "qemu CA 3", NULL, NULL, NULL, NULL,
+                 true, true, true,
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
+                 false, false, NULL, NULL,
+                 0, 0);
+    TLS_CERT_REQ(servercert3req, cacert3req,
+                 "UK", "qemu.org", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+                 0, 0);
+
+    TLS_TEST_REG(goodca1, true,
+                 cacert1req.filename, servercert1req.filename, false);
+    TLS_TEST_REG(goodca2, true,
+                 cacert2req.filename, servercert2req.filename, false);
+    TLS_TEST_REG(goodca3, true,
+                 cacert3req.filename, servercert3req.filename, false);
+
+    /* Now some bad certs */
+
+    /* Key usage:dig-sig:not-critical */
+    TLS_ROOT_REQ(cacert4req,
+                 "UK", "qemu CA 4", NULL, NULL, NULL, NULL,
+                 true, true, true,
+                 true, false, GNUTLS_KEY_DIGITAL_SIGNATURE,
+                 false, false, NULL, NULL,
+                 0, 0);
+    TLS_CERT_REQ(servercert4req, cacert4req,
+                 "UK", "qemu.org", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+                 0, 0);
+    /* no-basic */
+    TLS_ROOT_REQ(cacert5req,
+                 "UK", "qemu CA 5", NULL, NULL, NULL, NULL,
+                 false, false, false,
+                 false, false, 0,
+                 false, false, NULL, NULL,
+                 0, 0);
+    TLS_CERT_REQ(servercert5req, cacert5req,
+                 "UK", "qemu.org", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+                 0, 0);
+    /* Key usage:dig-sig:critical */
+    TLS_ROOT_REQ(cacert6req,
+                 "UK", "qemu CA 6", NULL, NULL, NULL, NULL,
+                 true, true, true,
+                 true, true, GNUTLS_KEY_DIGITAL_SIGNATURE,
+                 false, false, NULL, NULL,
+                 0, 0);
+    TLS_CERT_REQ(servercert6req, cacert6req,
+                 "UK", "qemu.org", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+                 0, 0);
+
+    /* Technically a CA cert with basic constraints
+     * key purpose == key signing + non-critical should
+     * be rejected. GNUTLS < 3.1 does not reject it and
+     * we don't anticipate them changing this behaviour
+     */
+    TLS_TEST_REG(badca1, true, cacert4req.filename, servercert4req.filename,
+                (GNUTLS_VERSION_MAJOR == 3 && GNUTLS_VERSION_MINOR >= 1) ||
+                GNUTLS_VERSION_MAJOR > 3);
+    TLS_TEST_REG(badca2, true,
+                 cacert5req.filename, servercert5req.filename, true);
+    TLS_TEST_REG(badca3, true,
+                 cacert6req.filename, servercert6req.filename, true);
+
+
+    /* Various good servers */
+    /* no usage or purpose */
+    TLS_CERT_REQ(servercert7req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 false, false, 0,
+                 false, false, NULL, NULL,
+                 0, 0);
+    /* usage:cert-sign+dig-sig+encipher:critical */
+    TLS_CERT_REQ(servercert8req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT |
+                 GNUTLS_KEY_KEY_CERT_SIGN,
+                 false, false, NULL, NULL,
+                 0, 0);
+    /* usage:cert-sign:not-critical */
+    TLS_CERT_REQ(servercert9req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, false, GNUTLS_KEY_KEY_CERT_SIGN,
+                 false, false, NULL, NULL,
+                 0, 0);
+    /* purpose:server:critical */
+    TLS_CERT_REQ(servercert10req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 false, false, 0,
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+                 0, 0);
+    /* purpose:server:not-critical */
+    TLS_CERT_REQ(servercert11req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 false, false, 0,
+                 true, false, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+                 0, 0);
+    /* purpose:client+server:critical */
+    TLS_CERT_REQ(servercert12req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 false, false, 0,
+                 true, true,
+                 GNUTLS_KP_TLS_WWW_CLIENT, GNUTLS_KP_TLS_WWW_SERVER,
+                 0, 0);
+    /* purpose:client+server:not-critical */
+    TLS_CERT_REQ(servercert13req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 false, false, 0,
+                 true, false,
+                 GNUTLS_KP_TLS_WWW_CLIENT, GNUTLS_KP_TLS_WWW_SERVER,
+                 0, 0);
+
+    TLS_TEST_REG(goodserver1, true,
+                 cacertreq.filename, servercert7req.filename, false);
+    TLS_TEST_REG(goodserver2, true,
+                 cacertreq.filename, servercert8req.filename, false);
+    TLS_TEST_REG(goodserver3, true,
+                 cacertreq.filename, servercert9req.filename, false);
+    TLS_TEST_REG(goodserver4, true,
+                 cacertreq.filename, servercert10req.filename, false);
+    TLS_TEST_REG(goodserver5, true,
+                 cacertreq.filename, servercert11req.filename, false);
+    TLS_TEST_REG(goodserver6, true,
+                 cacertreq.filename, servercert12req.filename, false);
+    TLS_TEST_REG(goodserver7, true,
+                 cacertreq.filename, servercert13req.filename, false);
+
+    /* Bad servers */
+
+    /* usage:cert-sign:critical */
+    TLS_CERT_REQ(servercert14req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
+                 false, false, NULL, NULL,
+                 0, 0);
+    /* purpose:client:critical */
+    TLS_CERT_REQ(servercert15req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 false, false, 0,
+                 true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
+                 0, 0);
+    /* usage: none:critical */
+    TLS_CERT_REQ(servercert16req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true, 0,
+                 false, false, NULL, NULL,
+                 0, 0);
+
+    TLS_TEST_REG(badserver1, true,
+                 cacertreq.filename, servercert14req.filename, true);
+    TLS_TEST_REG(badserver2, true,
+                 cacertreq.filename, servercert15req.filename, true);
+    TLS_TEST_REG(badserver3, true,
+                 cacertreq.filename, servercert16req.filename, true);
+
+
+
+    /* Various good clients */
+    /* no usage or purpose */
+    TLS_CERT_REQ(clientcert1req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 false, false, 0,
+                 false, false, NULL, NULL,
+                 0, 0);
+    /* usage:cert-sign+dig-sig+encipher:critical */
+    TLS_CERT_REQ(clientcert2req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT |
+                 GNUTLS_KEY_KEY_CERT_SIGN,
+                 false, false, NULL, NULL,
+                 0, 0);
+    /* usage:cert-sign:not-critical */
+    TLS_CERT_REQ(clientcert3req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, false, GNUTLS_KEY_KEY_CERT_SIGN,
+                 false, false, NULL, NULL,
+                 0, 0);
+    /* purpose:client:critical */
+    TLS_CERT_REQ(clientcert4req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 false, false, 0,
+                 true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
+                 0, 0);
+    /* purpose:client:not-critical */
+    TLS_CERT_REQ(clientcert5req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 false, false, 0,
+                 true, false, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
+                 0, 0);
+    /* purpose:client+client:critical */
+    TLS_CERT_REQ(clientcert6req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 false, false, 0,
+                 true, true,
+                 GNUTLS_KP_TLS_WWW_CLIENT, GNUTLS_KP_TLS_WWW_SERVER,
+                 0, 0);
+    /* purpose:client+client:not-critical */
+    TLS_CERT_REQ(clientcert7req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 false, false, 0,
+                 true, false,
+                 GNUTLS_KP_TLS_WWW_CLIENT, GNUTLS_KP_TLS_WWW_SERVER,
+                 0, 0);
+
+    TLS_TEST_REG(goodclient1, false,
+                 cacertreq.filename, clientcert1req.filename, false);
+    TLS_TEST_REG(goodclient2, false,
+                 cacertreq.filename, clientcert2req.filename, false);
+    TLS_TEST_REG(goodclient3, false,
+                 cacertreq.filename, clientcert3req.filename, false);
+    TLS_TEST_REG(goodclient4, false,
+                 cacertreq.filename, clientcert4req.filename, false);
+    TLS_TEST_REG(goodclient5, false,
+                 cacertreq.filename, clientcert5req.filename, false);
+    TLS_TEST_REG(goodclient6, false,
+                 cacertreq.filename, clientcert6req.filename, false);
+    TLS_TEST_REG(goodclient7, false,
+                 cacertreq.filename, clientcert7req.filename, false);
+
+    /* Bad clients */
+
+    /* usage:cert-sign:critical */
+    TLS_CERT_REQ(clientcert8req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
+                 false, false, NULL, NULL,
+                 0, 0);
+    /* purpose:client:critical */
+    TLS_CERT_REQ(clientcert9req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 false, false, 0,
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+                 0, 0);
+    /* usage: none:critical */
+    TLS_CERT_REQ(clientcert10req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true, 0,
+                 false, false, NULL, NULL,
+                 0, 0);
+
+    TLS_TEST_REG(badclient1, false,
+                 cacertreq.filename, clientcert8req.filename, true);
+    TLS_TEST_REG(badclient2, false,
+                 cacertreq.filename, clientcert9req.filename, true);
+    TLS_TEST_REG(badclient3, false,
+                 cacertreq.filename, clientcert10req.filename, true);
+
+
+
+    /* Expired stuff */
+
+    TLS_ROOT_REQ(cacertexpreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, true,
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
+                 false, false, NULL, NULL,
+                 0, -1);
+    TLS_CERT_REQ(servercertexpreq, cacertexpreq,
+                 "UK", "qemu.org", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+                 0, 0);
+    TLS_CERT_REQ(servercertexp1req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+                 0, -1);
+    TLS_CERT_REQ(clientcertexp1req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
+                 0, -1);
+
+    TLS_TEST_REG(expired1, true,
+                 cacertexpreq.filename, servercertexpreq.filename, true);
+    TLS_TEST_REG(expired2, true,
+                 cacertreq.filename, servercertexp1req.filename, true);
+    TLS_TEST_REG(expired3, false,
+                 cacertreq.filename, clientcertexp1req.filename, true);
+
+
+    /* Not activated stuff */
+
+    TLS_ROOT_REQ(cacertnewreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, true,
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
+                 false, false, NULL, NULL,
+                 1, 2);
+    TLS_CERT_REQ(servercertnewreq, cacertnewreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+                 0, 0);
+    TLS_CERT_REQ(servercertnew1req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+                 1, 2);
+    TLS_CERT_REQ(clientcertnew1req, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
+                 1, 2);
+
+    TLS_TEST_REG(inactive1, true,
+                 cacertnewreq.filename, servercertnewreq.filename, true);
+    TLS_TEST_REG(inactive2, true,
+                 cacertreq.filename, servercertnew1req.filename, true);
+    TLS_TEST_REG(inactive3, false,
+                 cacertreq.filename, clientcertnew1req.filename, true);
+
+    TLS_ROOT_REQ(cacertrootreq,
+                 "UK", "qemu root", NULL, NULL, NULL, NULL,
+                 true, true, true,
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
+                 false, false, NULL, NULL,
+                 0, 0);
+    TLS_CERT_REQ(cacertlevel1areq, cacertrootreq,
+                 "UK", "qemu level 1a", NULL, NULL, NULL, NULL,
+                 true, true, true,
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
+                 false, false, NULL, NULL,
+                 0, 0);
+    TLS_CERT_REQ(cacertlevel1breq, cacertrootreq,
+                 "UK", "qemu level 1b", NULL, NULL, NULL, NULL,
+                 true, true, true,
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
+                 false, false, NULL, NULL,
+                 0, 0);
+    TLS_CERT_REQ(cacertlevel2areq, cacertlevel1areq,
+                 "UK", "qemu level 2a", NULL, NULL, NULL, NULL,
+                 true, true, true,
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
+                 false, false, NULL, NULL,
+                 0, 0);
+    TLS_CERT_REQ(servercertlevel3areq, cacertlevel2areq,
+                 "UK", "qemu.org", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+                 0, 0);
+    TLS_CERT_REQ(clientcertlevel2breq, cacertlevel1breq,
+                 "UK", "qemu client level 2b", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
+                 0, 0);
+
+    gnutls_x509_crt_t certchain[] = {
+        cacertrootreq.crt,
+        cacertlevel1areq.crt,
+        cacertlevel1breq.crt,
+        cacertlevel2areq.crt,
+    };
+
+    test_tls_write_cert_chain(WORKDIR "cacertchain-ctx.pem",
+                              certchain,
+                              G_N_ELEMENTS(certchain));
+
+    TLS_TEST_REG(chain1, true,
+                 WORKDIR "cacertchain-ctx.pem",
+                 servercertlevel3areq.filename, false);
+    TLS_TEST_REG(chain2, false,
+                 WORKDIR "cacertchain-ctx.pem",
+                 clientcertlevel2breq.filename, false);
+
+    TLS_TEST_REG(missing, false,
+                 "cacertdoesnotexist.pem",
+                 "servercertdoesnotexist.pem", true);
+
+    ret = g_test_run();
+
+    test_tls_discard_cert(&cacertreq);
+    test_tls_discard_cert(&cacert1req);
+    test_tls_discard_cert(&cacert2req);
+    test_tls_discard_cert(&cacert3req);
+    test_tls_discard_cert(&cacert4req);
+    test_tls_discard_cert(&cacert5req);
+    test_tls_discard_cert(&cacert6req);
+
+    test_tls_discard_cert(&servercertreq);
+    test_tls_discard_cert(&servercert1req);
+    test_tls_discard_cert(&servercert2req);
+    test_tls_discard_cert(&servercert3req);
+    test_tls_discard_cert(&servercert4req);
+    test_tls_discard_cert(&servercert5req);
+    test_tls_discard_cert(&servercert6req);
+    test_tls_discard_cert(&servercert7req);
+    test_tls_discard_cert(&servercert8req);
+    test_tls_discard_cert(&servercert9req);
+    test_tls_discard_cert(&servercert10req);
+    test_tls_discard_cert(&servercert11req);
+    test_tls_discard_cert(&servercert12req);
+    test_tls_discard_cert(&servercert13req);
+    test_tls_discard_cert(&servercert14req);
+    test_tls_discard_cert(&servercert15req);
+    test_tls_discard_cert(&servercert16req);
+
+    test_tls_discard_cert(&clientcertreq);
+    test_tls_discard_cert(&clientcert1req);
+    test_tls_discard_cert(&clientcert2req);
+    test_tls_discard_cert(&clientcert3req);
+    test_tls_discard_cert(&clientcert4req);
+    test_tls_discard_cert(&clientcert5req);
+    test_tls_discard_cert(&clientcert6req);
+    test_tls_discard_cert(&clientcert7req);
+    test_tls_discard_cert(&clientcert8req);
+    test_tls_discard_cert(&clientcert9req);
+    test_tls_discard_cert(&clientcert10req);
+
+    test_tls_discard_cert(&cacertexpreq);
+    test_tls_discard_cert(&servercertexpreq);
+    test_tls_discard_cert(&servercertexp1req);
+    test_tls_discard_cert(&clientcertexp1req);
+
+    test_tls_discard_cert(&cacertnewreq);
+    test_tls_discard_cert(&servercertnewreq);
+    test_tls_discard_cert(&servercertnew1req);
+    test_tls_discard_cert(&clientcertnew1req);
+
+    test_tls_discard_cert(&cacertrootreq);
+    test_tls_discard_cert(&cacertlevel1areq);
+    test_tls_discard_cert(&cacertlevel1breq);
+    test_tls_discard_cert(&cacertlevel2areq);
+    test_tls_discard_cert(&servercertlevel3areq);
+    test_tls_discard_cert(&clientcertlevel2breq);
+    unlink(WORKDIR "cacertchain-ctx.pem");
+
+    test_tls_cleanup(KEYFILE);
+    rmdir(WORKDIR);
+
+    return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+#else /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */
+
+int
+main(void)
+{
+    return EXIT_SUCCESS;
+}
+
+#endif /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 16/34] crypto: introduce new module for handling TLS sessions
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (14 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 15/34] crypto: add sanity checking of " Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 17/34] block: convert quorum blockdrv to use crypto APIs Daniel P. Berrange
                   ` (18 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

Introduce a QCryptoTLSSession object that will encapsulate
all the code for setting up and using a client/sever TLS
session. This isolates the code which depends on the gnutls
library, avoiding #ifdefs in the rest of the codebase, as
well as facilitating any possible future port to other TLS
libraries, if desired.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 crypto/Makefile.objs           |   1 +
 crypto/tlssession.c            | 546 +++++++++++++++++++++++++++++++++++++++++
 include/crypto/tlssession.h    | 313 +++++++++++++++++++++++
 tests/.gitignore               |   4 +
 tests/Makefile                 |   3 +
 tests/test-crypto-tlssession.c | 540 ++++++++++++++++++++++++++++++++++++++++
 6 files changed, 1407 insertions(+)
 create mode 100644 crypto/tlssession.c
 create mode 100644 include/crypto/tlssession.h
 create mode 100644 tests/test-crypto-tlssession.c

diff --git a/crypto/Makefile.objs b/crypto/Makefile.objs
index cf62d51..e2e4348 100644
--- a/crypto/Makefile.objs
+++ b/crypto/Makefile.objs
@@ -4,3 +4,4 @@ util-obj-y += aes.o
 util-obj-y += desrfb.o
 util-obj-y += cipher.o
 util-obj-y += tlscreds.o
+util-obj-y += tlssession.o
diff --git a/crypto/tlssession.c b/crypto/tlssession.c
new file mode 100644
index 0000000..9d86de7
--- /dev/null
+++ b/crypto/tlssession.c
@@ -0,0 +1,546 @@
+/*
+ * QEMU crypto TLS session support
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "crypto/tlssession.h"
+#include "qemu/acl.h"
+
+#include <glib/gi18n.h>
+
+#ifdef CONFIG_GNUTLS
+
+/* #define QCRYPTO_DEBUG */
+
+#ifdef QCRYPTO_DEBUG
+#define DPRINTF(fmt, ...) do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do { } while (0)
+#endif
+
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+
+struct _QCryptoTLSSession {
+    QCryptoTLSCreds *creds;
+    gnutls_session_t handle;
+    char *hostname;
+    char *aclname;
+    bool handshakeComplete;
+    QCryptoTLSSessionWriteFunc writeFunc;
+    QCryptoTLSSessionReadFunc readFunc;
+    void *opaque;
+    char *peername;
+};
+
+
+void qcrypto_tls_session_free(QCryptoTLSSession *session)
+{
+    if (!session) {
+        return;
+    }
+
+    gnutls_deinit(session->handle);
+    g_free(session->hostname);
+    g_free(session->peername);
+    g_free(session->aclname);
+    object_unref(OBJECT(session->creds));
+    g_free(session);
+}
+
+
+static ssize_t
+qcrypto_tls_session_push(void *opaque, const void *buf, size_t len)
+{
+    QCryptoTLSSession *session = opaque;
+
+    if (!session->writeFunc) {
+        errno = EIO;
+        return -1;
+    };
+
+    return session->writeFunc(buf, len, session->opaque);
+}
+
+
+static ssize_t
+qcrypto_tls_session_pull(void *opaque, void *buf, size_t len)
+{
+    QCryptoTLSSession *session = opaque;
+
+    if (!session->readFunc) {
+        errno = EIO;
+        return -1;
+    };
+
+    return session->readFunc(buf, len, session->opaque);
+}
+
+
+QCryptoTLSSession *qcrypto_tls_session_new(QCryptoTLSCreds *creds,
+                                           const char *hostname,
+                                           const char *aclname,
+                                           QCryptoTLSCredsEndpoint endpoint,
+                                           Error **errp)
+{
+    QCryptoTLSSession *session;
+    int ret;
+
+    DPRINTF("New session creds=%p acl=%s\n", creds, aclname ? aclname : "none");
+    session = g_new0(QCryptoTLSSession, 1);
+    if (hostname) {
+        session->hostname = g_strdup(hostname);
+    }
+    if (aclname) {
+        session->aclname = g_strdup(aclname);
+    }
+    session->creds = creds;
+    object_ref(OBJECT(creds));
+
+    if (creds->endpoint != endpoint) {
+        error_setg(errp, "%s",
+                   _("Credentials endpoint doesn't match session"));
+        goto error;
+    }
+
+    if (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
+        ret = gnutls_init(&session->handle, GNUTLS_SERVER);
+    } else {
+        ret = gnutls_init(&session->handle, GNUTLS_CLIENT);
+    }
+    if (ret < 0) {
+        error_setg(errp, _("Cannot initialize TLS session: %s"),
+                   gnutls_strerror(ret));
+        goto error;
+    }
+
+    switch (creds->type) {
+    case QCRYPTO_TLS_CREDS_TYPE_ANON:
+        ret = gnutls_priority_set_direct(session->handle,
+                                         "NORMAL:+ANON-DH", NULL);
+        if (ret < 0) {
+            error_setg(errp, _("Unable to set TLS session priority: %s"),
+                       gnutls_strerror(ret));
+            goto error;
+        }
+        if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
+            ret = gnutls_credentials_set(session->handle,
+                                         GNUTLS_CRD_ANON,
+                                         creds->data.anonServer);
+        } else {
+            ret = gnutls_credentials_set(session->handle,
+                                         GNUTLS_CRD_ANON,
+                                         creds->data.anonClient);
+        }
+        if (ret < 0) {
+            error_setg(errp, _("Cannot set session credentials: %s"),
+                       gnutls_strerror(ret));
+            goto error;
+        }
+
+        break;
+
+    case QCRYPTO_TLS_CREDS_TYPE_X509:
+        ret = gnutls_set_default_priority(session->handle);
+        if (ret < 0) {
+            error_setg(errp, _("Cannot set default TLS session priority: %s"),
+                       gnutls_strerror(ret));
+            goto error;
+        }
+        ret = gnutls_credentials_set(session->handle,
+                                     GNUTLS_CRD_CERTIFICATE,
+                                     creds->data.x509);
+        if (ret < 0) {
+            error_setg(errp, _("Cannot set session credentials: %s"),
+                       gnutls_strerror(ret));
+            goto error;
+        }
+
+        if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
+            /* This requests, but does not enforce a client cert.
+             * The cert checking code later does enforcement */
+            gnutls_certificate_server_set_request(session->handle,
+                                                  GNUTLS_CERT_REQUEST);
+        }
+        break;
+
+    default:
+        error_setg(errp, _("Unsupported TLS credentials type %d"),
+                   creds->type);
+        goto error;
+    }
+
+    gnutls_transport_set_ptr(session->handle, session);
+    gnutls_transport_set_push_function(session->handle,
+                                       qcrypto_tls_session_push);
+    gnutls_transport_set_pull_function(session->handle,
+                                       qcrypto_tls_session_pull);
+
+    DPRINTF("Session %p\n", session);
+    return session;
+
+ error:
+    qcrypto_tls_session_free(session);
+    return NULL;
+}
+
+static int
+qcrypto_tls_session_check_certificate(QCryptoTLSSession *session,
+                                      Error **errp)
+{
+    int ret;
+    unsigned int status;
+    const gnutls_datum_t *certs;
+    unsigned int nCerts, i;
+    time_t now;
+
+    now = time(NULL);
+    if (now == ((time_t)-1)) {
+        error_setg_errno(errp, errno, _("Cannot get current time"));
+        return -1;
+    }
+
+    ret = gnutls_certificate_verify_peers2(session->handle, &status);
+    if (ret < 0) {
+        error_setg(errp, "Verify failed: %s", gnutls_strerror(ret));
+        return -1;
+    }
+
+    if (status != 0) {
+        const char *reason = _("Invalid certificate");
+
+        if (status & GNUTLS_CERT_INVALID) {
+            reason = _("The certificate is not trusted");
+        }
+
+        if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
+            reason = _("The certificate hasn't got a known issuer");
+        }
+
+        if (status & GNUTLS_CERT_REVOKED) {
+            reason = _("The certificate has been revoked");
+        }
+
+        if (status & GNUTLS_CERT_INSECURE_ALGORITHM) {
+            reason = _("The certificate uses an insecure algorithm");
+        }
+
+        error_setg(errp, "%s", reason);
+        return -1;
+    }
+
+    certs = gnutls_certificate_get_peers(session->handle, &nCerts);
+    if (!certs) {
+        error_setg(errp, "%s", _("No certificate peers"));
+        return -1;
+    }
+
+    for (i = 0 ; i < nCerts ; i++) {
+        gnutls_x509_crt_t cert;
+
+        if (gnutls_x509_crt_init(&cert) < 0) {
+            return -1;
+        }
+
+        if (gnutls_x509_crt_import(cert, &certs[i], GNUTLS_X509_FMT_DER) < 0) {
+            gnutls_x509_crt_deinit(cert);
+            return -1;
+        }
+
+        if (gnutls_x509_crt_get_expiration_time(cert) < now) {
+            error_setg(errp, _("The certificate has expired"));
+            gnutls_x509_crt_deinit(cert);
+            return -1;
+        }
+
+        if (gnutls_x509_crt_get_activation_time(cert) > now) {
+            error_setg(errp, _("The certificate is not yet activated"));
+            gnutls_x509_crt_deinit(cert);
+            return -1;
+        }
+
+        if (gnutls_x509_crt_get_activation_time(cert) > now) {
+            error_setg(errp, _("The certificate is not yet activated"));
+            gnutls_x509_crt_deinit(cert);
+            return -1;
+        }
+
+        if (i == 0) {
+            size_t dnameSize = 1024;
+            session->peername = g_malloc(dnameSize);
+        requery:
+            ret = gnutls_x509_crt_get_dn(cert, session->peername, &dnameSize);
+            if (ret < 0) {
+                if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) {
+                    session->peername = g_realloc(session->peername,
+                                                   dnameSize);
+                    goto requery;
+                }
+                error_setg(errp, _("Cannot get client distinguished name: %s"),
+                           gnutls_strerror(ret));
+                gnutls_x509_crt_deinit(cert);
+                return -1;
+            }
+            if (session->aclname) {
+                qemu_acl *acl = qemu_acl_find(session->aclname);
+                int allow;
+                if (!acl) {
+                    error_setg(errp, _("Cannot find ACL %s"),
+                               session->aclname);
+                    gnutls_x509_crt_deinit(cert);
+                    return -1;
+                }
+
+                allow = qemu_acl_party_is_allowed(acl, session->peername);
+
+                error_setg(errp, "TLS x509 ACL check for %s is %s",
+                           session->peername, allow ? "allowed" : "denied");
+                if (!allow) {
+                    gnutls_x509_crt_deinit(cert);
+                    return -1;
+                }
+            }
+            if (session->hostname) {
+                if (!gnutls_x509_crt_check_hostname(cert, session->hostname)) {
+                    error_setg(errp,
+                               _("Certificate does not match the hostname %s"),
+                               session->hostname);
+                    return -1;
+                }
+            }
+        }
+
+        gnutls_x509_crt_deinit(cert);
+    }
+
+    return 0;
+}
+
+int
+qcrypto_tls_session_check_credentials(QCryptoTLSSession *session,
+                                      Error **errp)
+{
+    switch (session->creds->type) {
+    case QCRYPTO_TLS_CREDS_TYPE_X509:
+        if (session->creds->verifyPeer) {
+            return qcrypto_tls_session_check_certificate(session,
+                                                         errp);
+        } else {
+            return 0;
+        }
+
+    case QCRYPTO_TLS_CREDS_TYPE_ANON:
+        return 0;
+
+    default:
+        error_setg(errp, _("Unexpected credential type %d"),
+                   session->creds->type);
+        return -1;
+    }
+}
+
+
+void qcrypto_tls_session_set_callbacks(QCryptoTLSSession *session,
+                                       QCryptoTLSSessionWriteFunc writeFunc,
+                                       QCryptoTLSSessionReadFunc readFunc,
+                                       void *opaque)
+{
+    session->writeFunc = writeFunc;
+    session->readFunc = readFunc;
+    session->opaque = opaque;
+}
+
+ssize_t qcrypto_tls_session_write(QCryptoTLSSession *session,
+                                  const char *buf,
+                                  size_t len)
+{
+    ssize_t ret = gnutls_record_send(session->handle, buf, len);
+
+    if (ret < 0) {
+        switch (ret) {
+        case GNUTLS_E_AGAIN:
+            errno = EAGAIN;
+            break;
+        case GNUTLS_E_INTERRUPTED:
+            errno = EINTR;
+            break;
+        default:
+            errno = EIO;
+            break;
+        }
+        ret = -1;
+    }
+
+    return ret;
+}
+
+ssize_t qcrypto_tls_session_read(QCryptoTLSSession *session,
+                                 char *buf,
+                                 size_t len)
+{
+    ssize_t ret = gnutls_record_recv(session->handle, buf, len);
+
+    if (ret < 0) {
+        switch (ret) {
+        case GNUTLS_E_AGAIN:
+            errno = EAGAIN;
+            break;
+        case GNUTLS_E_INTERRUPTED:
+            errno = EINTR;
+            break;
+        default:
+            errno = EIO;
+            break;
+        }
+        ret = -1;
+    }
+
+    return ret;
+}
+
+int qcrypto_tls_session_handshake(QCryptoTLSSession *session,
+                                  Error **errp)
+{
+    int ret = gnutls_handshake(session->handle);
+    DPRINTF("Handshake session=%p ret=%d\n", session, ret);
+    if (ret == 0) {
+        session->handshakeComplete = true;
+    } else {
+        if (ret == GNUTLS_E_INTERRUPTED ||
+            ret == GNUTLS_E_AGAIN) {
+            ret = 1;
+        } else {
+            error_setg(errp, _("TLS handshake failed: %s"),
+                       gnutls_strerror(ret));
+            ret = -1;
+        }
+    }
+
+    return ret;
+}
+
+
+QCryptoTLSSessionHandshakeStatus
+qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *session)
+{
+    if (session->handshakeComplete) {
+        return QCRYPTO_TLS_HANDSHAKE_COMPLETE;
+    } else if (gnutls_record_get_direction(session->handle) == 0) {
+        return QCRYPTO_TLS_HANDSHAKE_RECVING;
+    } else {
+        return QCRYPTO_TLS_HANDSHAKE_SENDING;
+    }
+}
+
+int qcrypto_tls_session_get_key_size(QCryptoTLSSession *session,
+                                     Error **errp)
+{
+    gnutls_cipher_algorithm_t cipher;
+    int ssf;
+
+    cipher = gnutls_cipher_get(session->handle);
+    ssf = gnutls_cipher_get_key_size(cipher);
+    if (!ssf) {
+        error_setg(errp, _("Cannot get TLS cipher key size"));
+        return -1;
+    }
+    return ssf;
+}
+
+char *qcrypto_tls_session_get_peer_name(QCryptoTLSSession *session)
+{
+    if (session->peername) {
+        return g_strdup(session->peername);
+    }
+    return NULL;
+}
+
+#else /* ! CONFIG_GNUTLS */
+
+QCryptoTLSSession *qcrypto_tls_session_new(
+    QCryptoTLSCreds *creds G_GNUC_UNUSED,
+    const char *hostname G_GNUC_UNUSED,
+    const char *aclname G_GNUC_UNUSED,
+    QCryptoTLSCredsEndpoint endpoint G_GNUC_UNUSED,
+    Error **errp)
+{
+    error_setg(errp, "%s", _("TLS requires GNUTLS support"));
+    return NULL;
+}
+
+void qcrypto_tls_session_free(QCryptoTLSSession *sess G_GNUC_UNUSED)
+{
+}
+
+int qcrypto_tls_session_check_credentials(
+    QCryptoTLSSession *sess G_GNUC_UNUSED,
+    Error **errp)
+{
+    error_setg(errp, "%s", _("TLS requires GNUTLS support"));
+    return -1;
+}
+
+void qcrypto_tls_session_set_callbacks(
+    QCryptoTLSSession *sess G_GNUC_UNUSED,
+    QCryptoTLSSessionWriteFunc writeFunc G_GNUC_UNUSED,
+    QCryptoTLSSessionReadFunc readFunc G_GNUC_UNUSED,
+    void *opaque G_GNUC_UNUSED)
+{
+}
+
+ssize_t qcrypto_tls_session_write(QCryptoTLSSession *sess,
+                                  const char *buf,
+                                  size_t len)
+{
+    errno = -EIO;
+    return -1;
+}
+ssize_t qcrypto_tls_session_read(QCryptoTLSSession *sess,
+                                 char *buf,
+                                 size_t len)
+{
+    errno = -EIO;
+    return -1;
+}
+
+int qcrypto_tls_session_handshake(QCryptoTLSSession *sess,
+                                  Error **errp)
+{
+    error_setg(errp, "%s", _("TLS requires GNUTLS support"));
+    return -1;
+}
+
+QCryptoTLSSessionHandshakeStatus
+qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess)
+{
+    return QCRYPTO_TLS_HANDSHAKE_COMPLETE;
+}
+
+int qcrypto_tls_session_get_key_size(QCryptoTLSSession *sess,
+                                     Error **errp)
+{
+    error_setg(errp, "%s", _("TLS requires GNUTLS support"));
+    return -1;
+}
+
+char *qcrypto_tls_session_get_peer_name(QCryptoTLSSession *sess)
+{
+    return NULL;
+}
+
+#endif
diff --git a/include/crypto/tlssession.h b/include/crypto/tlssession.h
new file mode 100644
index 0000000..703d1e1
--- /dev/null
+++ b/include/crypto/tlssession.h
@@ -0,0 +1,313 @@
+/*
+ * QEMU crypto TLS session support
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QCRYPTO_TLS_SESSION_H__
+#define QCRYPTO_TLS_SESSION_H__
+
+#include "crypto/tlscreds.h"
+
+/**
+ * QCryptoTLSSession:
+ *
+ * The QCryptoTLSSession object encapsulates the
+ * logic to integrate with a TLS providing library such
+ * as GNUTLS, to setup and run TLS sessions.
+ *
+ * The API is designed such that it has no assumption about
+ * the type of transport it is running over. It may be a
+ * traditional TCP socket, or something else entirely. The
+ * only requirement is a full-duplex stream of some kind.
+ *
+ * static int mysock_send(const char *buf, size_t len,
+ *                        void *opaque)
+ * {
+ *    int fd = GPOINTER_TO_INT(opaque);
+ *
+ *    return write(*fd, buf, len);
+ * }
+ *
+ * static int mysock_recv(const char *buf, size_t len,
+ *                        void *opaque)
+ * {
+ *    int fd = GPOINTER_TO_INT(opaque);
+ *
+ *    return read(*fd, buf, len);
+ * }
+ *
+ * static int mysock_run_tls(int sockfd,
+ *                           QCryptoTLSCreds *creds,
+ *                           Error *erp)
+ * {
+ *    QCryptoTLSSession *sess;
+ *
+ *    sess = qcrypto_tls_session_new(creds,
+ *                                   "vnc.example.com",
+ *                                   NULL,
+ *                                   QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT,
+ *                                   errp);
+ *    if (sess == NULL) {
+ *           return -1;
+ *    }
+ *
+ *    qcrypto_tls_session_set_callbacks(sess,
+ *                                      mysock_send,
+ *                                      mysock_recv
+ *                                      GINT_TO_POINTER(fd));
+ *
+ *    while (1) {
+ *       if (qcrypto_tls_session_handshake(sess, errp) < 0) {
+ *           qcrypto_tls_session_free(sess);
+ *           return -1;
+ *       }
+ *
+ *       switch(qcrypto_tls_session_get_handshake_status(sess)) {
+ *         case QCRYPTO_TLS_HANDSHAKE_COMPLETE:
+ *            goto done;
+ *         case QCRYPTO_TLS_HANDSHAKE_RECVING:
+ *            ...wait for GIO_IN event on fd...
+ *            break;
+ *         case QCRYPTO_TLS_HANDSHAKE_SENDING:
+ *            ...wait for GIO_OUT event on fd...
+ *            break;
+ *       }
+ *    }
+ *   done:
+ *
+ *    ....send/recv payload data on sess...
+ *
+ *    qcrypto_tls_session_free(sess):
+ * }
+ */
+
+typedef struct _QCryptoTLSSession QCryptoTLSSession;
+
+
+/**
+ * qcrypto_tls_session_new:
+ * @creds: pointer to a TLS credentials object
+ * @hostname: optional hostname to validate
+ * @aclname: optional ACL to validate peer credentials against
+ * @endpoint: role of the TLS session, client or server
+ * @errp: pointer to an uninitialized error object
+ *
+ * Create a new TLS session object that will be used to
+ * negotiate a TLS session over an arbitrary data channel.
+ * The session object can operate as either the server or
+ * client, according to the value of the @endpoint argument.
+ *
+ * For clients, the @hostname parameter should hold the full
+ * unmodified hostname as requested by the user. This will
+ * be used to verify the against the hostname reported in
+ * the server's credentials (aka x509 certificate).
+ *
+ * The @aclname parameter (optionally) specifies the name
+ * of an access controll list that will be used to validate
+ * the peer's credentials. For x509 credentials, the ACL
+ * will be matched against the CommonName shown in the peer's
+ * certificate. If the session is acting as a server, setting
+ * an ACL will require that the client provide a validate
+ * x509 client certificate.
+ *
+ * After creating the session object, the I/O callbacks
+ * must be set using the qcrypto_tls_session_set_callbacks()
+ * method. A TLS handshake sequence must then be completed
+ * using qcrypto_tls_session_handshake(), before payload
+ * data is permitted to be sent/received.
+ *
+ * The session object must be released by calling
+ * qcrypto_tls_session_free() when no longer required
+ *
+ * Returns: a TLS session object, or NULL on error.
+ */
+QCryptoTLSSession *qcrypto_tls_session_new(QCryptoTLSCreds *creds,
+                                           const char *hostname,
+                                           const char *aclname,
+                                           QCryptoTLSCredsEndpoint endpoint,
+                                           Error **errp);
+
+/**
+ * qcrypto_tls_session_free:
+ * @sess: the TLS session object
+ *
+ * Release all memory associated with the TLS session
+ * object previously allocated by qcrypto_tls_session_new()
+ */
+void qcrypto_tls_session_free(QCryptoTLSSession *sess);
+
+/**
+ * qcrypto_tls_session_check_credentials:
+ * @sess: the TLS session object
+ * @errp: pointer to an uninitialized error object
+ *
+ * Validate the peer's credentials after a successful
+ * TLS handshake. It is an error to call this before
+ * qcrypto_tls_session_get_handshake_status() returns
+ * QCRYPTO_TLS_HANDSHAKE_COMPLEET
+ *
+ * Returns 0 if the credentials validated, -1 on error
+ */
+int qcrypto_tls_session_check_credentials(QCryptoTLSSession *sess,
+                                          Error **errp);
+
+typedef ssize_t (*QCryptoTLSSessionWriteFunc)(const char *buf,
+                                              size_t len,
+                                              void *opaque);
+typedef ssize_t (*QCryptoTLSSessionReadFunc)(char *buf,
+                                             size_t len,
+                                             void *opaque);
+
+/**
+ * qcrypto_tls_session_set_callbacks:
+ * @sess: the TLS session object
+ * @writeFunc: callback for sending data
+ * @readFunc: callback to receiving data
+ * @opaque: data to pass to callbacks
+ *
+ * Sets the callback functions that are to be used for sending
+ * and receiving data on the underlying data channel. Typically
+ * the callbacks to write/read to/from a TCP socket, but there
+ * is no assumption made about the type of channel used.
+ *
+ * The @writeFunc callback will be passed the encrypted
+ * data to send to the remote peer.
+ *
+ * The @readFunc callback will be passed a pointer to fill
+ * with encrypted data received fro mthe remote peer
+ */
+void qcrypto_tls_session_set_callbacks(QCryptoTLSSession *sess,
+                                       QCryptoTLSSessionWriteFunc writeFunc,
+                                       QCryptoTLSSessionReadFunc readFunc,
+                                       void *opaque);
+
+/**
+ * qcrypto_tls_session_write:
+ * @sess: the TLS session object
+ * @buf: the plain text to send
+ * @len: the length of @buf
+ *
+ * Encrypt @len bytes of the data in @buf and send
+ * it to the remote peer using the callback previously
+ * registered with qcrypto_tls_session_set_callbacks()
+ *
+ * It is an error to call this before
+ * qcrypto_tls_session_get_handshake_status() returns
+ * QCRYPTO_TLS_HANDSHAKE_COMPLETE
+ *
+ * Returns: the number of bytes sent, or -1 on error
+ */
+ssize_t qcrypto_tls_session_write(QCryptoTLSSession *sess,
+                                  const char *buf,
+                                  size_t len);
+
+/**
+ * qcrypto_tls_session_read:
+ * @sess: the TLS session object
+ * @buf: to fill with plain text received
+ * @len: the length of @buf
+ *
+ * Receive upto @len bytes of data from the remote peer
+ * using the callback previously registered with
+ * qcrypto_tls_session_set_callbacks(), decrypt it and
+ * store it in @buf.
+ *
+ * It is an error to call this before
+ * qcrypto_tls_session_get_handshake_status() returns
+ * QCRYPTO_TLS_HANDSHAKE_COMPLETE
+ *
+ * Returns: the number of bytes received, or -1 on error
+ */
+ssize_t qcrypto_tls_session_read(QCryptoTLSSession *sess,
+                                 char *buf,
+                                 size_t len);
+
+/**
+ * qcrypto_tls_session_handshake:
+ * @sess: the TLS session object
+ * @errp: pointer to an uninitialized error object
+ *
+ * Start, or continue, a TLS handshake sequence. If
+ * the underlying data channel is non-blocking, then
+ * this method may return control before the handshake
+ * is complete. On non-blocking channels the
+ * qcrypto_tls_session_get_handshake_status() method
+ * should be used to determine whether the handshake
+ * has completed, or is waiting to send or receive
+ * data. In the latter cases, the caller should setup
+ * an event loop watch and call this method again
+ * once the underlying data channel is ready to read
+ * or write again
+ */
+int qcrypto_tls_session_handshake(QCryptoTLSSession *sess,
+                                  Error **errp);
+
+typedef enum {
+    QCRYPTO_TLS_HANDSHAKE_COMPLETE,
+    QCRYPTO_TLS_HANDSHAKE_SENDING,
+    QCRYPTO_TLS_HANDSHAKE_RECVING,
+} QCryptoTLSSessionHandshakeStatus;
+
+/**
+ * qcrypto_tls_session_get_handshake_status:
+ * @sess: the TLS session object
+ *
+ * Check the status of the TLS handshake. This
+ * is used with non-blocking data channels to
+ * determine whether the handshake is waiting
+ * to send or receive further data to/from the
+ * remote peer.
+ *
+ * Once this returns QCRYPTO_TLS_HANDSHAKE_COMPLETE
+ * it is permitted to send/receive payload data on
+ * the channel
+ */
+QCryptoTLSSessionHandshakeStatus
+qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess);
+
+/**
+ * qcrypto_tls_session_get_key_size:
+ * @sess: the TLS session object
+ * @errp: pointer to an uninitialized error object
+ *
+ * Check the size of the data channel encryption key
+ *
+ * Returns: the length in bytes of the encryption key
+ * or -1 on error
+ */
+int qcrypto_tls_session_get_key_size(QCryptoTLSSession *sess,
+                                     Error **errp);
+
+/**
+ * qcrypto_tls_session_get_peer_name:
+ * @sess: the TLS session object
+ *
+ * Get the identified name of the remote peer. If the
+ * TLS session was negotiated using x509 certificate
+ * credentials, this will return the CommonName from
+ * the peer's certificate. If no identified name is
+ * available it will return NULL.
+ *
+ * The returned data must be released with g_free()
+ * when no longer required.
+ *
+ * Returns: the peer's name or NULL.
+ */
+char *qcrypto_tls_session_get_peer_name(QCryptoTLSSession *sess);
+
+#endif /* QCRYPTO_TLS_SESSION_H__ */
diff --git a/tests/.gitignore b/tests/.gitignore
index ad64e38..c9e2aa0 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -14,6 +14,10 @@ test-crypto-hash
 test-crypto-tlscreds
 test-crypto-tlscreds-work/
 test-crypto-tlscreds-certdir/
+test-crypto-tlssession
+test-crypto-tlssession-work/
+test-crypto-tlssession-client/
+test-crypto-tlssession-server/
 test-cutils
 test-hbitmap
 test-int128
diff --git a/tests/Makefile b/tests/Makefile
index d3aa188..92f3f47 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -75,6 +75,7 @@ gcov-files-test-write-threshold-y = block/write-threshold.c
 check-unit-$(CONFIG_GNUTLS_HASH) += tests/test-crypto-hash$(EXESUF)
 check-unit-y += tests/test-crypto-cipher$(EXESUF)
 check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlscreds$(EXESUF)
+check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlssession$(EXESUF)
 
 check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
 
@@ -316,6 +317,8 @@ tests/test-crypto-hash$(EXESUF): tests/test-crypto-hash.o $(qom-core-obj) libqem
 tests/test-crypto-cipher$(EXESUF): tests/test-crypto-cipher.o $(qom-core-obj) libqemuutil.a libqemustub.a
 tests/test-crypto-tlscreds$(EXESUF): tests/test-crypto-tlscreds.o tests/crypto-tls-helpers.o tests/pkix_asn1_tab.o \
         crypto/tlscreds.o $(qom-core-obj) libqemuutil.a libqemustub.a
+tests/test-crypto-tlssession$(EXESUF): tests/test-crypto-tlssession.o tests/crypto-tls-helpers.o tests/pkix_asn1_tab.o \
+        crypto/tlscreds.o crypto/tlssession.o $(qom-core-obj) libqemuutil.a libqemustub.a
 
 libqos-obj-y = tests/libqos/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o
 libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o
diff --git a/tests/test-crypto-tlssession.c b/tests/test-crypto-tlssession.c
new file mode 100644
index 0000000..b8a78c0
--- /dev/null
+++ b/tests/test-crypto-tlssession.c
@@ -0,0 +1,540 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daniel P. Berrange <berrange@redhat.com>
+ */
+
+
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+
+#include "config-host.h"
+#include "crypto-tls-helpers.h"
+#include "crypto/tlscreds.h"
+#include "crypto/tlssession.h"
+#include "qom/object_interfaces.h"
+#include "qemu/sockets.h"
+#include "qemu/acl.h"
+
+#ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
+
+#define WORKDIR "tests/test-io-channel-tls-work/"
+#define KEYFILE WORKDIR "key-ctx.pem"
+
+struct QCryptoTLSSessionTestData {
+    const char *servercacrt;
+    const char *clientcacrt;
+    const char *servercrt;
+    const char *clientcrt;
+    bool expectServerFail;
+    bool expectClientFail;
+    const char *hostname;
+    const char *const *wildcards;
+};
+
+
+static ssize_t testWrite(const char *buf, size_t len, void *opaque)
+{
+    int *fd = opaque;
+
+    return write(*fd, buf, len);
+}
+
+static ssize_t testRead(char *buf, size_t len, void *opaque)
+{
+    int *fd = opaque;
+
+    return read(*fd, buf, len);
+}
+
+static QCryptoTLSCreds *test_tls_creds_create(QCryptoTLSCredsEndpoint endpoint,
+                                              const char *certdir,
+                                              Error **errp)
+{
+    Object *creds = object_new_propv(
+        TYPE_QCRYPTO_TLS_CREDS,
+        (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
+         "testtlscredsserver" : "testtlscredsclient"),
+        errp,
+        "credtype", "x509",
+        "endpoint", (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER ?
+                     "server" : "client"),
+        "dir", certdir,
+        "verify-peer", "yes",
+        /* We skip initial sanity checks here because we
+         * want to make sure that problems are being
+         * detected at the TLS session validation stage,
+         * and the test-crypto-tlscreds test already
+         * validate the sanity check code.
+         */
+        "sanity-check", "no",
+        NULL
+        );
+
+    if (*errp) {
+        return NULL;
+    }
+    return QCRYPTO_TLS_CREDS(creds);
+}
+
+
+/*
+ * This tests validation checking of peer certificates
+ *
+ * This is replicating the checks that are done for an
+ * active TLS session after handshake completes. To
+ * simulate that we create our TLS contexts, skipping
+ * sanity checks. When then get a socketpair, and
+ * initiate a TLS session across them. Finally do
+ * do actual cert validation tests
+ */
+static void test_crypto_tls_session(const void *opaque)
+{
+    struct QCryptoTLSSessionTestData *data =
+        (struct QCryptoTLSSessionTestData *)opaque;
+    QCryptoTLSCreds *clientCreds;
+    QCryptoTLSCreds *serverCreds;
+    QCryptoTLSSession *clientSess = NULL;
+    QCryptoTLSSession *serverSess = NULL;
+    qemu_acl *acl;
+    const char * const *wildcards;
+    int channel[2];
+    bool clientShake = false;
+    bool serverShake = false;
+    Error *err = NULL;
+
+    /* We'll use this for our fake client-server connection */
+    g_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, channel) == 0);
+
+    /*
+     * We have an evil loop to do the handshake in a single
+     * thread, so we need these non-blocking to avoid deadlock
+     * of ourselves
+     */
+    qemu_set_nonblock(channel[0]);
+    qemu_set_nonblock(channel[1]);
+
+#define CLIENT_CERT_DIR "tests/test-crypto-tlssession-client/"
+#define SERVER_CERT_DIR "tests/test-crypto-tlssession-server/"
+    mkdir(CLIENT_CERT_DIR, 0700);
+    mkdir(SERVER_CERT_DIR, 0700);
+
+    unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
+    unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
+    unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
+
+    unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
+    unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
+    unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
+
+    g_assert(link(data->servercacrt,
+                  SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT) == 0);
+    g_assert(link(data->servercrt,
+                  SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT) == 0);
+    g_assert(link(KEYFILE,
+                  SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY) == 0);
+
+    g_assert(link(data->clientcacrt,
+                  CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT) == 0);
+    g_assert(link(data->clientcrt,
+                  CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT) == 0);
+    g_assert(link(KEYFILE,
+                  CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY) == 0);
+
+    clientCreds = test_tls_creds_create(
+        QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT,
+        CLIENT_CERT_DIR,
+        &err);
+    serverCreds = test_tls_creds_create(
+        QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
+        SERVER_CERT_DIR,
+        &err);
+
+    g_assert(clientCreds != NULL);
+    g_assert(serverCreds != NULL);
+
+    acl = qemu_acl_init("tlssessionacl");
+    qemu_acl_reset(acl);
+    wildcards = data->wildcards;
+    while (wildcards && *wildcards) {
+        qemu_acl_append(acl, 0, *wildcards);
+        wildcards++;
+    }
+
+    /* Now the real part of the test, setup the sessions */
+    clientSess = qcrypto_tls_session_new(
+        clientCreds, data->hostname, NULL,
+        QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT, &err);
+    serverSess = qcrypto_tls_session_new(
+        serverCreds, NULL,
+        data->wildcards ? "tlssessionacl" : NULL,
+        QCRYPTO_TLS_CREDS_ENDPOINT_SERVER, &err);
+
+    g_assert(clientSess != NULL);
+    g_assert(serverSess != NULL);
+
+    /* For handshake to work, we need to set the I/O callbacks
+     * to read/write over the socketpair
+     */
+    qcrypto_tls_session_set_callbacks(serverSess,
+                                      testWrite, testRead,
+                                      &channel[0]);
+    qcrypto_tls_session_set_callbacks(clientSess,
+                                      testWrite, testRead,
+                                      &channel[1]);
+
+    /*
+     * Finally we loop around & around doing handshake on each
+     * session until we get an error, or the handshake completes.
+     * This relies on the socketpair being nonblocking to avoid
+     * deadlocking ourselves upon handshake
+     */
+    do {
+        int rv;
+        if (!serverShake) {
+            rv = qcrypto_tls_session_handshake(serverSess,
+                                               &err);
+            g_assert(rv >= 0);
+            if (qcrypto_tls_session_get_handshake_status(serverSess) ==
+                QCRYPTO_TLS_HANDSHAKE_COMPLETE) {
+                serverShake = true;
+            }
+        }
+        if (!clientShake) {
+            rv = qcrypto_tls_session_handshake(clientSess,
+                                               &err);
+            g_assert(rv >= 0);
+            if (qcrypto_tls_session_get_handshake_status(clientSess) ==
+                QCRYPTO_TLS_HANDSHAKE_COMPLETE) {
+                clientShake = true;
+            }
+        }
+    } while (!clientShake && !serverShake);
+
+
+    /* Finally make sure the server validation does what
+     * we were expecting
+     */
+    if (qcrypto_tls_session_check_credentials(serverSess, &err) < 0) {
+        g_assert(data->expectServerFail);
+        error_free(err);
+        err = NULL;
+    } else {
+        g_assert(!data->expectServerFail);
+    }
+
+    /*
+     * And the same for the client validation check
+     */
+    if (qcrypto_tls_session_check_credentials(clientSess, &err) < 0) {
+        g_assert(data->expectClientFail);
+        error_free(err);
+        err = NULL;
+    } else {
+        g_assert(!data->expectClientFail);
+    }
+
+    unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
+    unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_CERT);
+    unlink(SERVER_CERT_DIR QCRYPTO_TLS_CREDS_X509_SERVER_KEY);
+
+    unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CA_CERT);
+    unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_CERT);
+    unlink(CLIENT_CERT_DIR QCRYPTO_TLS_CREDS_X509_CLIENT_KEY);
+
+    rmdir(CLIENT_CERT_DIR);
+    rmdir(SERVER_CERT_DIR);
+
+    object_unref(OBJECT(serverCreds));
+    object_property_del(
+        container_get(object_get_root(), "/objects"),
+        "testtlscredsserver", NULL);
+
+    object_unref(OBJECT(clientCreds));
+    object_property_del(
+        container_get(object_get_root(), "/objects"),
+        "testtlscredsclient", NULL);
+
+    qcrypto_tls_session_free(serverSess);
+    qcrypto_tls_session_free(clientSess);
+
+    close(channel[0]);
+    close(channel[1]);
+}
+
+
+int main(int argc, char **argv)
+{
+    int ret;
+
+    module_call_init(MODULE_INIT_QOM);
+    g_test_init(&argc, &argv, NULL);
+    qcrypto_tls_creds_dummy();
+    setenv("GNUTLS_FORCE_FIPS_MODE", "2", 1);
+
+    mkdir(WORKDIR, 0700);
+
+    test_tls_init(KEYFILE);
+
+# define TEST_SESS_REG(name, caCrt,                                     \
+                       serverCrt, clientCrt,                            \
+                       expectServerFail, expectClientFail,              \
+                       hostname, wildcards)                             \
+    struct QCryptoTLSSessionTestData name = {                           \
+        caCrt, caCrt, serverCrt, clientCrt,                             \
+        expectServerFail, expectClientFail,                             \
+        hostname, wildcards                                             \
+    };                                                                  \
+    g_test_add_data_func("/qcrypto/tlssession/" # name,                 \
+                         &name, test_crypto_tls_session);               \
+
+
+# define TEST_SESS_REG_EXT(name, serverCaCrt, clientCaCrt,              \
+                           serverCrt, clientCrt,                        \
+                           expectServerFail, expectClientFail,          \
+                           hostname, wildcards)                         \
+    struct QCryptoTLSSessionTestData name = {                           \
+        serverCaCrt, clientCaCrt, serverCrt, clientCrt,                 \
+        expectServerFail, expectClientFail,                             \
+        hostname, wildcards                                             \
+    };                                                                  \
+    g_test_add_data_func("/qcrypto/tlssession/" # name,                 \
+                         &name, test_crypto_tls_session);               \
+
+    /* A perfect CA, perfect client & perfect server */
+
+    /* Basic:CA:critical */
+    TLS_ROOT_REQ(cacertreq,
+                 "UK", "qemu CA", NULL, NULL, NULL, NULL,
+                 true, true, true,
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
+                 false, false, NULL, NULL,
+                 0, 0);
+
+    TLS_ROOT_REQ(altcacertreq,
+                 "UK", "qemu CA 1", NULL, NULL, NULL, NULL,
+                 true, true, true,
+                 false, false, 0,
+                 false, false, NULL, NULL,
+                 0, 0);
+
+    TLS_CERT_REQ(servercertreq, cacertreq,
+                 "UK", "qemu.org", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+                 0, 0);
+    TLS_CERT_REQ(clientcertreq, cacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
+                 0, 0);
+
+    TLS_CERT_REQ(clientcertaltreq, altcacertreq,
+                 "UK", "qemu", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
+                 0, 0);
+
+    TEST_SESS_REG(basicca, cacertreq.filename,
+                  servercertreq.filename, clientcertreq.filename,
+                  false, false, "qemu.org", NULL);
+    TEST_SESS_REG_EXT(differentca, cacertreq.filename,
+                      altcacertreq.filename, servercertreq.filename,
+                      clientcertaltreq.filename, true, true, "qemu.org", NULL);
+
+
+    /* When an altname is set, the CN is ignored, so it must be duplicated
+     * as an altname for it to match */
+    TLS_CERT_REQ(servercertalt1req, cacertreq,
+                 "UK", "qemu.org", "www.qemu.org", "qemu.org",
+                 "192.168.122.1", "fec0::dead:beaf",
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+                 0, 0);
+    /* This intentionally doesn't replicate */
+    TLS_CERT_REQ(servercertalt2req, cacertreq,
+                 "UK", "qemu.org", "www.qemu.org", "wiki.qemu.org",
+                 "192.168.122.1", "fec0::dead:beaf",
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+                 0, 0);
+
+    TEST_SESS_REG(altname1, cacertreq.filename,
+                  servercertalt1req.filename, clientcertreq.filename,
+                  false, false, "qemu.org", NULL);
+    TEST_SESS_REG(altname2, cacertreq.filename,
+                  servercertalt1req.filename, clientcertreq.filename,
+                  false, false, "www.qemu.org", NULL);
+    TEST_SESS_REG(altname3, cacertreq.filename,
+                  servercertalt1req.filename, clientcertreq.filename,
+                  false, true, "wiki.qemu.org", NULL);
+
+    TEST_SESS_REG(altname4, cacertreq.filename,
+                  servercertalt2req.filename, clientcertreq.filename,
+                  false, true, "qemu.org", NULL);
+    TEST_SESS_REG(altname5, cacertreq.filename,
+                  servercertalt2req.filename, clientcertreq.filename,
+                  false, false, "www.qemu.org", NULL);
+    TEST_SESS_REG(altname6, cacertreq.filename,
+                  servercertalt2req.filename, clientcertreq.filename,
+                  false, false, "wiki.qemu.org", NULL);
+
+    const char *const wildcards1[] = {
+        "C=UK,CN=dogfood",
+        NULL,
+    };
+    const char *const wildcards2[] = {
+        "C=UK,CN=qemu",
+        NULL,
+    };
+    const char *const wildcards3[] = {
+        "C=UK,CN=dogfood",
+        "C=UK,CN=qemu",
+        NULL,
+    };
+    const char *const wildcards4[] = {
+        "C=UK,CN=qemustuff",
+        NULL,
+    };
+    const char *const wildcards5[] = {
+        "C=UK,CN=qemu*",
+        NULL,
+    };
+    const char *const wildcards6[] = {
+        "C=UK,CN=*emu*",
+        NULL,
+    };
+
+    TEST_SESS_REG(wildcard1, cacertreq.filename,
+                  servercertreq.filename, clientcertreq.filename,
+                  true, false, "qemu.org", wildcards1);
+    TEST_SESS_REG(wildcard2, cacertreq.filename,
+                  servercertreq.filename, clientcertreq.filename,
+                  false, false, "qemu.org", wildcards2);
+    TEST_SESS_REG(wildcard3, cacertreq.filename,
+                  servercertreq.filename, clientcertreq.filename,
+                  false, false, "qemu.org", wildcards3);
+    TEST_SESS_REG(wildcard4, cacertreq.filename,
+                  servercertreq.filename, clientcertreq.filename,
+                  true, false, "qemu.org", wildcards4);
+    TEST_SESS_REG(wildcard5, cacertreq.filename,
+                  servercertreq.filename, clientcertreq.filename,
+                  false, false, "qemu.org", wildcards5);
+    TEST_SESS_REG(wildcard6, cacertreq.filename,
+                  servercertreq.filename, clientcertreq.filename,
+                  false, false, "qemu.org", wildcards6);
+
+    TLS_ROOT_REQ(cacertrootreq,
+                 "UK", "qemu root", NULL, NULL, NULL, NULL,
+                 true, true, true,
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
+                 false, false, NULL, NULL,
+                 0, 0);
+    TLS_CERT_REQ(cacertlevel1areq, cacertrootreq,
+                 "UK", "qemu level 1a", NULL, NULL, NULL, NULL,
+                 true, true, true,
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
+                 false, false, NULL, NULL,
+                 0, 0);
+    TLS_CERT_REQ(cacertlevel1breq, cacertrootreq,
+                 "UK", "qemu level 1b", NULL, NULL, NULL, NULL,
+                 true, true, true,
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
+                 false, false, NULL, NULL,
+                 0, 0);
+    TLS_CERT_REQ(cacertlevel2areq, cacertlevel1areq,
+                 "UK", "qemu level 2a", NULL, NULL, NULL, NULL,
+                 true, true, true,
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
+                 false, false, NULL, NULL,
+                 0, 0);
+    TLS_CERT_REQ(servercertlevel3areq, cacertlevel2areq,
+                 "UK", "qemu.org", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
+                 0, 0);
+    TLS_CERT_REQ(clientcertlevel2breq, cacertlevel1breq,
+                 "UK", "qemu client level 2b", NULL, NULL, NULL, NULL,
+                 true, true, false,
+                 true, true,
+                 GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
+                 true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
+                 0, 0);
+
+    gnutls_x509_crt_t certchain[] = {
+        cacertrootreq.crt,
+        cacertlevel1areq.crt,
+        cacertlevel1breq.crt,
+        cacertlevel2areq.crt,
+    };
+
+    test_tls_write_cert_chain(WORKDIR "cacertchain-sess.pem",
+                              certchain,
+                              G_N_ELEMENTS(certchain));
+
+    TEST_SESS_REG(cachain, WORKDIR "cacertchain-sess.pem",
+                  servercertlevel3areq.filename, clientcertlevel2breq.filename,
+                  false, false, "qemu.org", NULL);
+
+    ret = g_test_run();
+
+    test_tls_discard_cert(&clientcertreq);
+    test_tls_discard_cert(&clientcertaltreq);
+
+    test_tls_discard_cert(&servercertreq);
+    test_tls_discard_cert(&servercertalt1req);
+    test_tls_discard_cert(&servercertalt2req);
+
+    test_tls_discard_cert(&cacertreq);
+    test_tls_discard_cert(&altcacertreq);
+
+    test_tls_discard_cert(&cacertrootreq);
+    test_tls_discard_cert(&cacertlevel1areq);
+    test_tls_discard_cert(&cacertlevel1breq);
+    test_tls_discard_cert(&cacertlevel2areq);
+    test_tls_discard_cert(&servercertlevel3areq);
+    test_tls_discard_cert(&clientcertlevel2breq);
+    unlink(WORKDIR "cacertchain-sess.pem");
+
+    test_tls_cleanup(KEYFILE);
+    rmdir(WORKDIR);
+
+    return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+#else /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */
+
+int
+main(void)
+{
+    return EXIT_SUCCESS;
+}
+
+#endif /* ! QCRYPTO_HAVE_TLS_TEST_SUPPORT */
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 17/34] block: convert quorum blockdrv to use crypto APIs
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (15 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 16/34] crypto: introduce new module for handling TLS sessions Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 18/34] ui: convert VNC websockets " Daniel P. Berrange
                   ` (17 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

Get rid of direct use of gnutls APIs in quorum blockdrv in
favour of using the crypto APIs. This avoids the need to
do conditional compilation of the quorum driver. It can
simply report an error at file open file instead if the
required hash algorithm isn't supported by QEMU.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 block/Makefile.objs |  2 +-
 block/quorum.c      | 38 +++++++++++++++++++-------------------
 configure           | 39 ---------------------------------------
 3 files changed, 20 insertions(+), 59 deletions(-)

diff --git a/block/Makefile.objs b/block/Makefile.objs
index db2933e..2b4210d 100644
--- a/block/Makefile.objs
+++ b/block/Makefile.objs
@@ -3,7 +3,7 @@ block-obj-y += qcow2.o qcow2-refcount.o qcow2-cluster.o qcow2-snapshot.o qcow2-c
 block-obj-y += qed.o qed-gencb.o qed-l2-cache.o qed-table.o qed-cluster.o
 block-obj-y += qed-check.o
 block-obj-$(CONFIG_VHDX) += vhdx.o vhdx-endian.o vhdx-log.o
-block-obj-$(CONFIG_QUORUM) += quorum.o
+block-obj-y += quorum.o
 block-obj-y += parallels.o blkdebug.o blkverify.o
 block-obj-y += block-backend.o snapshot.o qapi.o
 block-obj-$(CONFIG_WIN32) += raw-win32.o win32-aio.o
diff --git a/block/quorum.c b/block/quorum.c
index 437b122..86375b3 100644
--- a/block/quorum.c
+++ b/block/quorum.c
@@ -13,8 +13,6 @@
  * See the COPYING file in the top-level directory.
  */
 
-#include <gnutls/gnutls.h>
-#include <gnutls/crypto.h>
 #include "block/block_int.h"
 #include "qapi/qmp/qbool.h"
 #include "qapi/qmp/qdict.h"
@@ -23,6 +21,9 @@
 #include "qapi/qmp/qlist.h"
 #include "qapi/qmp/qstring.h"
 #include "qapi-event.h"
+#include "crypto/hash.h"
+
+#include <glib/gi18n.h>
 
 #define HASH_LENGTH 32
 
@@ -33,7 +34,7 @@
 
 /* This union holds a vote hash value */
 typedef union QuorumVoteValue {
-    char h[HASH_LENGTH];       /* SHA-256 hash */
+    uint8_t h[HASH_LENGTH];    /* SHA-256 hash */
     int64_t l;                 /* simpler 64 bits hash */
 } QuorumVoteValue;
 
@@ -430,25 +431,18 @@ static void quorum_free_vote_list(QuorumVotes *votes)
 
 static int quorum_compute_hash(QuorumAIOCB *acb, int i, QuorumVoteValue *hash)
 {
-    int j, ret;
-    gnutls_hash_hd_t dig;
     QEMUIOVector *qiov = &acb->qcrs[i].qiov;
-
-    ret = gnutls_hash_init(&dig, GNUTLS_DIG_SHA256);
-
-    if (ret < 0) {
-        return ret;
+    size_t len = sizeof(hash->h);
+    uint8_t *data = hash->h;
+
+    if (qcrypto_hash_bytesv(QCRYPTO_HASH_ALG_SHA256,
+                            qiov->iov, qiov->niov,
+                            &data, &len,
+                            NULL) < 0) {
+        return -EINVAL;
     }
 
-    for (j = 0; j < qiov->niov; j++) {
-        ret = gnutls_hash(dig, qiov->iov[j].iov_base, qiov->iov[j].iov_len);
-        if (ret < 0) {
-            break;
-        }
-    }
-
-    gnutls_hash_deinit(dig, (void *) hash);
-    return ret;
+    return 0;
 }
 
 static QuorumVoteVersion *quorum_get_vote_winner(QuorumVotes *votes)
@@ -875,6 +869,12 @@ static int quorum_open(BlockDriverState *bs, QDict *options, int flags,
     int i;
     int ret = 0;
 
+    if (!qcrypto_hash_supports(QCRYPTO_HASH_ALG_SHA256)) {
+        error_setg(errp, "%s",
+                   _("SHA256 hash support is required for quorum device"));
+        return -EINVAL;
+    }
+
     qdict_flatten(options);
     qdict_extract_subqdict(options, &sub, "children.");
     qdict_array_split(sub, &list);
diff --git a/configure b/configure
index f7ec73d..afca51e 100755
--- a/configure
+++ b/configure
@@ -336,7 +336,6 @@ vte=""
 tpm="yes"
 libssh2=""
 vhdx=""
-quorum=""
 numa=""
 
 # parse CC options first
@@ -1135,10 +1134,6 @@ for opt do
   ;;
   --disable-vhdx) vhdx="no"
   ;;
-  --disable-quorum) quorum="no"
-  ;;
-  --enable-quorum) quorum="yes"
-  ;;
   --disable-numa) numa="no"
   ;;
   --enable-numa) numa="yes"
@@ -1414,8 +1409,6 @@ Advanced options (experts only):
   --enable-libssh2         enable ssh block device support
   --disable-vhdx           disable support for the Microsoft VHDX image format
   --enable-vhdx            enable support for the Microsoft VHDX image format
-  --disable-quorum         disable quorum block filter support
-  --enable-quorum          enable quorum block filter support
   --disable-numa           disable libnuma support
   --enable-numa            enable libnuma support
 
@@ -2412,33 +2405,6 @@ EOF
 fi
 
 ##########################################
-# Quorum probe (check for gnutls)
-if test "$quorum" != "no" ; then
-cat > $TMPC <<EOF
-#include <gnutls/gnutls.h>
-#include <gnutls/crypto.h>
-int main(void) {char data[4096], digest[32];
-gnutls_hash_fast(GNUTLS_DIG_SHA256, data, 4096, digest);
-return 0;
-}
-EOF
-quorum_tls_cflags=`$pkg_config --cflags gnutls 2> /dev/null`
-quorum_tls_libs=`$pkg_config --libs gnutls 2> /dev/null`
-if compile_prog "$quorum_tls_cflags" "$quorum_tls_libs" ; then
-  qcow_tls=yes
-  libs_softmmu="$quorum_tls_libs $libs_softmmu"
-  libs_tools="$quorum_tls_libs $libs_softmmu"
-  QEMU_CFLAGS="$QEMU_CFLAGS $quorum_tls_cflags"
-  quorum="yes"
-else
-  if test "$quorum" = "yes"; then
-    feature_not_found "gnutls" "gnutls > 2.10.0 required to compile Quorum"
-  fi
-  quorum="no"
-fi
-fi
-
-##########################################
 # VNC SASL detection
 if test "$vnc" = "yes" -a "$vnc_sasl" != "no" ; then
   cat > $TMPC <<EOF
@@ -4545,7 +4511,6 @@ echo "libssh2 support   $libssh2"
 echo "TPM passthrough   $tpm_passthrough"
 echo "QOM debugging     $qom_cast_debug"
 echo "vhdx              $vhdx"
-echo "Quorum            $quorum"
 echo "lzo support       $lzo"
 echo "snappy support    $snappy"
 echo "bzip2 support     $bzip2"
@@ -5023,10 +4988,6 @@ if test "$libssh2" = "yes" ; then
   echo "LIBSSH2_LIBS=$libssh2_libs" >> $config_host_mak
 fi
 
-if test "$quorum" = "yes" ; then
-  echo "CONFIG_QUORUM=y" >> $config_host_mak
-fi
-
 if test "$vhdx" = "yes" ; then
   echo "CONFIG_VHDX=y" >> $config_host_mak
 fi
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 18/34] ui: convert VNC websockets to use crypto APIs
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (16 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 17/34] block: convert quorum blockdrv to use crypto APIs Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 19/34] block: convert qcow/qcow2 to use generic cipher API Daniel P. Berrange
                   ` (16 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

Remove the direct use of gnutls for hash processing in the
websockets code, in favour of using the crypto APIs. This
allows the websockets code to be built unconditionally
removing countless conditional checks from the VNC code.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 configure        | 19 +--------------
 ui/Makefile.objs |  2 +-
 ui/vnc-ws.c      | 22 ++++++++----------
 ui/vnc-ws.h      |  2 --
 ui/vnc.c         | 70 ++++++++++++--------------------------------------------
 ui/vnc.h         |  8 -------
 6 files changed, 26 insertions(+), 97 deletions(-)

diff --git a/configure b/configure
index afca51e..fa07f2c 100755
--- a/configure
+++ b/configure
@@ -245,7 +245,6 @@ vnc_tls=""
 vnc_sasl=""
 vnc_jpeg=""
 vnc_png=""
-vnc_ws=""
 xen=""
 xen_ctrl_version=""
 xen_pci_passthrough=""
@@ -889,10 +888,6 @@ for opt do
   ;;
   --enable-vnc-png) vnc_png="yes"
   ;;
-  --disable-vnc-ws) vnc_ws="no"
-  ;;
-  --enable-vnc-ws) vnc_ws="yes"
-  ;;
   --disable-slirp) slirp="no"
   ;;
   --disable-uuid) uuid="no"
@@ -2376,7 +2371,7 @@ fi
 
 ##########################################
 # VNC TLS/WS detection
-if test "$vnc" = "yes" -a \( "$vnc_tls" != "no" -o "$vnc_ws" != "no" \) ; then
+if test "$vnc" = "yes" -a "$vnc_tls" != "no" ; then
   cat > $TMPC <<EOF
 #include <gnutls/gnutls.h>
 int main(void) { gnutls_session_t s; gnutls_init(&s, GNUTLS_SERVER); return 0; }
@@ -2387,20 +2382,13 @@ EOF
     if test "$vnc_tls" != "no" ; then
       vnc_tls=yes
     fi
-    if test "$vnc_ws" != "no" ; then
-      vnc_ws=yes
-    fi
     libs_softmmu="$vnc_tls_libs $libs_softmmu"
     QEMU_CFLAGS="$QEMU_CFLAGS $vnc_tls_cflags"
   else
     if test "$vnc_tls" = "yes" ; then
       feature_not_found "vnc-tls" "Install gnutls devel"
     fi
-    if test "$vnc_ws" = "yes" ; then
-      feature_not_found "vnc-ws" "Install gnutls devel"
-    fi
     vnc_tls=no
-    vnc_ws=no
   fi
 fi
 
@@ -4448,7 +4436,6 @@ if test "$vnc" = "yes" ; then
     echo "VNC SASL support  $vnc_sasl"
     echo "VNC JPEG support  $vnc_jpeg"
     echo "VNC PNG support   $vnc_png"
-    echo "VNC WS support    $vnc_ws"
 fi
 if test -n "$sparc_cpu"; then
     echo "Target Sparc Arch $sparc_cpu"
@@ -4653,10 +4640,6 @@ fi
 if test "$vnc_png" = "yes" ; then
   echo "CONFIG_VNC_PNG=y" >> $config_host_mak
 fi
-if test "$vnc_ws" = "yes" ; then
-  echo "CONFIG_VNC_WS=y" >> $config_host_mak
-  echo "VNC_WS_CFLAGS=$vnc_ws_cflags" >> $config_host_mak
-fi
 if test "$fnmatch" = "yes" ; then
   echo "CONFIG_FNMATCH=y" >> $config_host_mak
 fi
diff --git a/ui/Makefile.objs b/ui/Makefile.objs
index ca3ca38..6458d3c 100644
--- a/ui/Makefile.objs
+++ b/ui/Makefile.objs
@@ -4,7 +4,7 @@ vnc-obj-y += vnc-enc-tight.o vnc-palette.o
 vnc-obj-y += vnc-enc-zrle.o
 vnc-obj-$(CONFIG_VNC_TLS) += vnc-tls.o vnc-auth-vencrypt.o
 vnc-obj-$(CONFIG_VNC_SASL) += vnc-auth-sasl.o
-vnc-obj-$(CONFIG_VNC_WS) += vnc-ws.o
+vnc-obj-y += vnc-ws.o
 vnc-obj-y += vnc-jobs.o
 
 common-obj-y += keymaps.o console.o cursor.o qemu-pixman.o
diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c
index 62eb97f..93a7b35 100644
--- a/ui/vnc-ws.c
+++ b/ui/vnc-ws.c
@@ -20,6 +20,7 @@
 
 #include "vnc.h"
 #include "qemu/main-loop.h"
+#include "crypto/hash.h"
 
 #ifdef CONFIG_VNC_TLS
 #include "qemu/sockets.h"
@@ -203,24 +204,21 @@ static char *vncws_extract_handshake_entry(const char *handshake,
 static void vncws_send_handshake_response(VncState *vs, const char* key)
 {
     char combined_key[WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1];
-    unsigned char hash[SHA1_DIGEST_LEN];
-    size_t hash_size = sizeof(hash);
     char *accept = NULL, *response = NULL;
-    gnutls_datum_t in;
-    int ret;
+    Error *err = NULL;
 
     g_strlcpy(combined_key, key, WS_CLIENT_KEY_LEN + 1);
     g_strlcat(combined_key, WS_GUID, WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1);
 
     /* hash and encode it */
-    in.data = (void *)combined_key;
-    in.size = WS_CLIENT_KEY_LEN + WS_GUID_LEN;
-    ret = gnutls_fingerprint(GNUTLS_DIG_SHA1, &in, hash, &hash_size);
-    if (ret == GNUTLS_E_SUCCESS && hash_size <= SHA1_DIGEST_LEN) {
-        accept = g_base64_encode(hash, hash_size);
-    }
-    if (accept == NULL) {
-        VNC_DEBUG("Hashing Websocket combined key failed\n");
+    if (qcrypto_hash_base64(QCRYPTO_HASH_ALG_SHA1,
+                            combined_key,
+                            WS_CLIENT_KEY_LEN + WS_GUID_LEN,
+                            &accept,
+                            &err) < 0) {
+        VNC_DEBUG("Hashing Websocket combined key failed %s\n",
+                  error_get_pretty(err));
+        error_free(err);
         vnc_client_error(vs);
         return;
     }
diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h
index 14d4230..9494225 100644
--- a/ui/vnc-ws.h
+++ b/ui/vnc-ws.h
@@ -21,8 +21,6 @@
 #ifndef __QEMU_UI_VNC_WS_H
 #define __QEMU_UI_VNC_WS_H
 
-#include <gnutls/gnutls.h>
-
 #define B64LEN(__x) (((__x + 2) / 3) * 12 / 3)
 #define SHA1_DIGEST_LEN 20
 
diff --git a/ui/vnc.c b/ui/vnc.c
index 39402d3..ef0d87d 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -38,6 +38,7 @@
 #include "qemu/osdep.h"
 #include "ui/input.h"
 #include "qapi-event.h"
+#include "crypto/hash.h"
 
 #define VNC_REFRESH_INTERVAL_BASE GUI_REFRESH_INTERVAL_DEFAULT
 #define VNC_REFRESH_INTERVAL_INC  50
@@ -353,9 +354,7 @@ static VncClientInfo *qmp_query_vnc_client(const VncState *client)
     info->base->host = g_strdup(host);
     info->base->service = g_strdup(serv);
     info->base->family = inet_netfamily(sa.ss_family);
-#ifdef CONFIG_VNC_WS
     info->base->websocket = client->websocket;
-#endif
 
 #ifdef CONFIG_VNC_TLS
     if (client->tls.session && client->tls.dname) {
@@ -580,12 +579,10 @@ VncInfo2List *qmp_query_vnc_servers(Error **errp)
             info->server = qmp_query_server_entry(vd->lsock, false,
                                                   info->server);
         }
-#ifdef CONFIG_VNC_WS
         if (vd->lwebsock != -1) {
             info->server = qmp_query_server_entry(vd->lwebsock, true,
                                                   info->server);
         }
-#endif
 
         item = g_new0(VncInfo2List, 1);
         item->value = info;
@@ -1229,10 +1226,8 @@ void vnc_disconnect_finish(VncState *vs)
 
     buffer_free(&vs->input);
     buffer_free(&vs->output);
-#ifdef CONFIG_VNC_WS
     buffer_free(&vs->ws_input);
     buffer_free(&vs->ws_output);
-#endif /* CONFIG_VNC_WS */
 
     qapi_free_VncClientInfo(vs->info);
 
@@ -1411,12 +1406,9 @@ static void vnc_client_write_locked(void *opaque)
     } else
 #endif /* CONFIG_VNC_SASL */
     {
-#ifdef CONFIG_VNC_WS
         if (vs->encode_ws) {
             vnc_client_write_ws(vs);
-        } else
-#endif /* CONFIG_VNC_WS */
-        {
+        } else {
             vnc_client_write_plain(vs);
         }
     }
@@ -1427,11 +1419,7 @@ void vnc_client_write(void *opaque)
     VncState *vs = opaque;
 
     vnc_lock_output(vs);
-    if (vs->output.offset
-#ifdef CONFIG_VNC_WS
-            || vs->ws_output.offset
-#endif
-            ) {
+    if (vs->output.offset || vs->ws_output.offset) {
         vnc_client_write_locked(opaque);
     } else if (vs->csock != -1) {
         qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
@@ -1537,7 +1525,6 @@ void vnc_client_read(void *opaque)
         ret = vnc_client_read_sasl(vs);
     else
 #endif /* CONFIG_VNC_SASL */
-#ifdef CONFIG_VNC_WS
         if (vs->encode_ws) {
             ret = vnc_client_read_ws(vs);
             if (ret == -1) {
@@ -1547,10 +1534,8 @@ void vnc_client_read(void *opaque)
                 vnc_client_error(vs);
                 return;
             }
-        } else
-#endif /* CONFIG_VNC_WS */
-        {
-        ret = vnc_client_read_plain(vs);
+        } else {
+            ret = vnc_client_read_plain(vs);
         }
     if (!ret) {
         if (vs->csock == -1)
@@ -1622,11 +1607,8 @@ void vnc_write_u8(VncState *vs, uint8_t value)
 void vnc_flush(VncState *vs)
 {
     vnc_lock_output(vs);
-    if (vs->csock != -1 && (vs->output.offset
-#ifdef CONFIG_VNC_WS
-                || vs->ws_output.offset
-#endif
-                )) {
+    if (vs->csock != -1 && (vs->output.offset ||
+                            vs->ws_output.offset)) {
         vnc_client_write_locked(vs);
     }
     vnc_unlock_output(vs);
@@ -3017,7 +2999,6 @@ static void vnc_connect(VncDisplay *vd, int csock,
     VNC_DEBUG("New client on socket %d\n", csock);
     update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE);
     qemu_set_nonblock(vs->csock);
-#ifdef CONFIG_VNC_WS
     if (websocket) {
         vs->websocket = 1;
 #ifdef CONFIG_VNC_TLS
@@ -3030,9 +3011,7 @@ static void vnc_connect(VncDisplay *vd, int csock,
             qemu_set_fd_handler2(vs->csock, NULL, vncws_handshake_read,
                                  NULL, vs);
         }
-    } else
-#endif /* CONFIG_VNC_WS */
-    {
+    } else {
         qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
     }
 
@@ -3040,10 +3019,7 @@ static void vnc_connect(VncDisplay *vd, int csock,
     vnc_qmp_event(vs, QAPI_EVENT_VNC_CONNECTED);
     vnc_set_share_mode(vs, VNC_SHARE_MODE_CONNECTING);
 
-#ifdef CONFIG_VNC_WS
-    if (!vs->websocket)
-#endif
-    {
+    if (!vs->websocket) {
         vnc_init_state(vs);
     }
 
@@ -3099,12 +3075,9 @@ static void vnc_listen_read(void *opaque, bool websocket)
 
     /* Catch-up */
     graphic_hw_update(vs->dcl.con);
-#ifdef CONFIG_VNC_WS
     if (websocket) {
         csock = qemu_accept(vs->lwebsock, (struct sockaddr *)&addr, &addrlen);
-    } else
-#endif /* CONFIG_VNC_WS */
-    {
+    } else {
         csock = qemu_accept(vs->lsock, (struct sockaddr *)&addr, &addrlen);
     }
 
@@ -3119,12 +3092,10 @@ static void vnc_listen_regular_read(void *opaque)
     vnc_listen_read(opaque, false);
 }
 
-#ifdef CONFIG_VNC_WS
 static void vnc_listen_websocket_read(void *opaque)
 {
     vnc_listen_read(opaque, true);
 }
-#endif /* CONFIG_VNC_WS */
 
 static const DisplayChangeListenerOps dcl_ops = {
     .dpy_name             = "vnc",
@@ -3150,9 +3121,7 @@ void vnc_display_init(const char *id)
     QTAILQ_INSERT_TAIL(&vnc_displays, vs, next);
 
     vs->lsock = -1;
-#ifdef CONFIG_VNC_WS
     vs->lwebsock = -1;
-#endif
 
     QTAILQ_INIT(&vs->clients);
     vs->expires = TIME_MAX;
@@ -3186,14 +3155,12 @@ static void vnc_display_close(VncDisplay *vs)
         close(vs->lsock);
         vs->lsock = -1;
     }
-#ifdef CONFIG_VNC_WS
     vs->ws_enabled = false;
     if (vs->lwebsock != -1) {
         qemu_set_fd_handler2(vs->lwebsock, NULL, NULL, NULL, NULL);
         close(vs->lwebsock);
         vs->lwebsock = -1;
     }
-#endif /* CONFIG_VNC_WS */
     vs->auth = VNC_AUTH_INVALID;
     vs->subauth = VNC_AUTH_INVALID;
 #ifdef CONFIG_VNC_TLS
@@ -3572,13 +3539,12 @@ void vnc_display_open(const char *id, Error **errp)
 
     websocket = qemu_opt_get(opts, "websocket");
     if (websocket) {
-#ifdef CONFIG_VNC_WS
         vs->ws_enabled = true;
         qemu_opt_set(wsopts, "port", websocket, &error_abort);
-#else /* ! CONFIG_VNC_WS */
-        error_setg(errp, "Websockets protocol requires gnutls support");
-        goto fail;
-#endif /* ! CONFIG_VNC_WS */
+        if (!qcrypto_hash_supports(QCRYPTO_HASH_ALG_SHA1)) {
+            error_setg(errp, "SHA1 hash support is required for websockets");
+            goto fail;
+        }
     }
 
 #ifdef CONFIG_VNC_JPEG
@@ -3661,9 +3627,7 @@ void vnc_display_open(const char *id, Error **errp)
         /* connect to viewer */
         int csock;
         vs->lsock = -1;
-#ifdef CONFIG_VNC_WS
         vs->lwebsock = -1;
-#endif
         if (strncmp(vnc, "unix:", 5) == 0) {
             csock = unix_connect(vnc+5, errp);
         } else {
@@ -3683,7 +3647,6 @@ void vnc_display_open(const char *id, Error **errp)
             if (vs->lsock < 0) {
                 goto fail;
             }
-#ifdef CONFIG_VNC_WS
             if (vs->ws_enabled) {
                 vs->lwebsock = inet_listen_opts(wsopts, 0, errp);
                 if (vs->lwebsock < 0) {
@@ -3694,17 +3657,14 @@ void vnc_display_open(const char *id, Error **errp)
                     goto fail;
                 }
             }
-#endif /* CONFIG_VNC_WS */
         }
         vs->enabled = true;
         qemu_set_fd_handler2(vs->lsock, NULL,
                 vnc_listen_regular_read, NULL, vs);
-#ifdef CONFIG_VNC_WS
         if (vs->ws_enabled) {
             qemu_set_fd_handler2(vs->lwebsock, NULL,
                     vnc_listen_websocket_read, NULL, vs);
         }
-#endif /* CONFIG_VNC_WS */
     }
     qemu_opts_del(sopts);
     qemu_opts_del(wsopts);
@@ -3714,9 +3674,7 @@ fail:
     qemu_opts_del(sopts);
     qemu_opts_del(wsopts);
     vs->enabled = false;
-#ifdef CONFIG_VNC_WS
     vs->ws_enabled = false;
-#endif /* CONFIG_VNC_WS */
 }
 
 void vnc_display_add_client(const char *id, int csock, bool skipauth)
diff --git a/ui/vnc.h b/ui/vnc.h
index 3f7c6a9..814d720 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -108,9 +108,7 @@ typedef struct VncDisplay VncDisplay;
 #ifdef CONFIG_VNC_SASL
 #include "vnc-auth-sasl.h"
 #endif
-#ifdef CONFIG_VNC_WS
 #include "vnc-ws.h"
-#endif
 
 struct VncRectStat
 {
@@ -156,10 +154,8 @@ struct VncDisplay
     int connections_limit;
     VncSharePolicy share_policy;
     int lsock;
-#ifdef CONFIG_VNC_WS
     int lwebsock;
     bool ws_enabled;
-#endif
     DisplaySurface *ds;
     DisplayChangeListener dcl;
     kbd_layout_t *kbd_layout;
@@ -294,21 +290,17 @@ struct VncState
 #ifdef CONFIG_VNC_SASL
     VncStateSASL sasl;
 #endif
-#ifdef CONFIG_VNC_WS
     bool encode_ws;
     bool websocket;
-#endif /* CONFIG_VNC_WS */
 
     VncClientInfo *info;
 
     Buffer output;
     Buffer input;
-#ifdef CONFIG_VNC_WS
     Buffer ws_input;
     Buffer ws_output;
     size_t ws_payload_remain;
     WsMask ws_payload_mask;
-#endif
     /* current output mode information */
     VncWritePixels *write_pixels;
     PixelFormat client_pf;
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 19/34] block: convert qcow/qcow2 to use generic cipher API
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (17 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 18/34] ui: convert VNC websockets " Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 20/34] ui: convert VNC " Daniel P. Berrange
                   ` (15 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

Switch the qcow/qcow2 block driver over to use the generic cipher
API, this allows it to use the pluggable AES implementations,
instead of being hardcoded to use QEMU's built-in impl.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 block/qcow.c          | 100 ++++++++++++++++++++++++++++++++++++--------------
 block/qcow2-cluster.c |  46 ++++++++++++++++++-----
 block/qcow2.c         |  94 ++++++++++++++++++++++++-----------------------
 block/qcow2.h         |  13 +++----
 4 files changed, 162 insertions(+), 91 deletions(-)

diff --git a/block/qcow.c b/block/qcow.c
index cb07e8d..dd0cfbf 100644
--- a/block/qcow.c
+++ b/block/qcow.c
@@ -25,7 +25,7 @@
 #include "block/block_int.h"
 #include "qemu/module.h"
 #include <zlib.h>
-#include "crypto/aes.h"
+#include "crypto/cipher.h"
 #include "migration/migration.h"
 
 /**************************************************************/
@@ -71,10 +71,8 @@ typedef struct BDRVQcowState {
     uint8_t *cluster_cache;
     uint8_t *cluster_data;
     uint64_t cluster_cache_offset;
-    uint32_t crypt_method; /* current crypt method, 0 if no key yet */
+    QCryptoCipher *cipher; /* NULL if no key yet */
     uint32_t crypt_method_header;
-    AES_KEY aes_encrypt_key;
-    AES_KEY aes_decrypt_key;
     CoMutex lock;
     Error *migration_blocker;
 } BDRVQcowState;
@@ -153,6 +151,11 @@ static int qcow_open(BlockDriverState *bs, QDict *options, int flags,
         ret = -EINVAL;
         goto fail;
     }
+    if (!qcrypto_cipher_supports(QCRYPTO_CIPHER_ALG_AES)) {
+        error_setg(errp, "AES cipher not available");
+        ret = -EINVAL;
+        goto fail;
+    }
     s->crypt_method_header = header.crypt_method;
     if (s->crypt_method_header) {
         bs->encrypted = 1;
@@ -259,6 +262,7 @@ static int qcow_set_key(BlockDriverState *bs, const char *key)
     BDRVQcowState *s = bs->opaque;
     uint8_t keybuf[16];
     int len, i;
+    Error *err;
 
     memset(keybuf, 0, 16);
     len = strlen(key);
@@ -269,38 +273,66 @@ static int qcow_set_key(BlockDriverState *bs, const char *key)
     for(i = 0;i < len;i++) {
         keybuf[i] = key[i];
     }
-    s->crypt_method = s->crypt_method_header;
 
-    if (AES_set_encrypt_key(keybuf, 128, &s->aes_encrypt_key) != 0)
-        return -1;
-    if (AES_set_decrypt_key(keybuf, 128, &s->aes_decrypt_key) != 0)
+    if (s->cipher) {
+        qcrypto_cipher_free(s->cipher);
+    }
+    s->cipher = qcrypto_cipher_new(
+        QCRYPTO_CIPHER_ALG_AES,
+        QCRYPTO_CIPHER_MODE_CBC,
+        keybuf, G_N_ELEMENTS(keybuf),
+        &err);
+
+    if (!s->cipher) {
+        error_free(err);
         return -1;
+    }
     return 0;
 }
 
 /* The crypt function is compatible with the linux cryptoloop
    algorithm for < 4 GB images. NOTE: out_buf == in_buf is
    supported */
-static void encrypt_sectors(BDRVQcowState *s, int64_t sector_num,
-                            uint8_t *out_buf, const uint8_t *in_buf,
-                            int nb_sectors, int enc,
-                            const AES_KEY *key)
+static int encrypt_sectors(BDRVQcowState *s, int64_t sector_num,
+                           uint8_t *out_buf, const uint8_t *in_buf,
+                           int nb_sectors, bool enc, Error **errp)
 {
     union {
         uint64_t ll[2];
         uint8_t b[16];
     } ivec;
     int i;
+    int ret;
 
     for(i = 0; i < nb_sectors; i++) {
         ivec.ll[0] = cpu_to_le64(sector_num);
         ivec.ll[1] = 0;
-        AES_cbc_encrypt(in_buf, out_buf, 512, key,
-                        ivec.b, enc);
+        if (qcrypto_cipher_setiv(s->cipher,
+                                 ivec.b, G_N_ELEMENTS(ivec.b),
+                                 errp) < 0) {
+            return -1;
+        }
+        if (enc) {
+            ret = qcrypto_cipher_encrypt(s->cipher,
+                                         in_buf,
+                                         out_buf,
+                                         512,
+                                         errp);
+        } else {
+            ret = qcrypto_cipher_decrypt(s->cipher,
+                                         in_buf,
+                                         out_buf,
+                                         512,
+                                         errp);
+        }
+        if (ret < 0) {
+            return -1;
+        }
         sector_num++;
         in_buf += 512;
         out_buf += 512;
     }
+    return 0;
 }
 
 /* 'allocate' is:
@@ -411,17 +443,22 @@ static uint64_t get_cluster_offset(BlockDriverState *bs,
                 bdrv_truncate(bs->file, cluster_offset + s->cluster_size);
                 /* if encrypted, we must initialize the cluster
                    content which won't be written */
-                if (s->crypt_method &&
+                if (s->cipher &&
                     (n_end - n_start) < s->cluster_sectors) {
                     uint64_t start_sect;
                     start_sect = (offset & ~(s->cluster_size - 1)) >> 9;
                     memset(s->cluster_data + 512, 0x00, 512);
                     for(i = 0; i < s->cluster_sectors; i++) {
                         if (i < n_start || i >= n_end) {
-                            encrypt_sectors(s, start_sect + i,
-                                            s->cluster_data,
-                                            s->cluster_data + 512, 1, 1,
-                                            &s->aes_encrypt_key);
+                            Error *err = NULL;
+                            if (encrypt_sectors(s, start_sect + i,
+                                                s->cluster_data,
+                                                s->cluster_data + 512, 1,
+                                                true, &err) < 0) {
+                                error_free(err);
+                                errno = EIO;
+                                return -1;
+                            }
                             if (bdrv_pwrite(bs->file, cluster_offset + i * 512,
                                             s->cluster_data, 512) != 512)
                                 return -1;
@@ -461,7 +498,7 @@ static int64_t coroutine_fn qcow_co_get_block_status(BlockDriverState *bs,
     if (!cluster_offset) {
         return 0;
     }
-    if ((cluster_offset & QCOW_OFLAG_COMPRESSED) || s->crypt_method) {
+    if ((cluster_offset & QCOW_OFLAG_COMPRESSED) || s->cipher) {
         return BDRV_BLOCK_DATA;
     }
     cluster_offset |= (index_in_cluster << BDRV_SECTOR_BITS);
@@ -528,6 +565,7 @@ static coroutine_fn int qcow_co_readv(BlockDriverState *bs, int64_t sector_num,
     QEMUIOVector hd_qiov;
     uint8_t *buf;
     void *orig_buf;
+    Error *err = NULL;
 
     if (qiov->niov > 1) {
         buf = orig_buf = qemu_try_blockalign(bs, qiov->size);
@@ -590,10 +628,11 @@ static coroutine_fn int qcow_co_readv(BlockDriverState *bs, int64_t sector_num,
             if (ret < 0) {
                 break;
             }
-            if (s->crypt_method) {
-                encrypt_sectors(s, sector_num, buf, buf,
-                                n, 0,
-                                &s->aes_decrypt_key);
+            if (s->cipher) {
+                if (encrypt_sectors(s, sector_num, buf, buf,
+                                    n, false, &err) < 0) {
+                    goto fail;
+                }
             }
         }
         ret = 0;
@@ -614,6 +653,7 @@ done:
     return ret;
 
 fail:
+    error_free(err);
     ret = -EIO;
     goto done;
 }
@@ -661,12 +701,17 @@ static coroutine_fn int qcow_co_writev(BlockDriverState *bs, int64_t sector_num,
             ret = -EIO;
             break;
         }
-        if (s->crypt_method) {
+        if (s->cipher) {
+            Error *err = NULL;
             if (!cluster_data) {
                 cluster_data = g_malloc0(s->cluster_size);
             }
-            encrypt_sectors(s, sector_num, cluster_data, buf,
-                            n, 1, &s->aes_encrypt_key);
+            if (encrypt_sectors(s, sector_num, cluster_data, buf,
+                                n, true, &err) < 0) {
+                error_free(err);
+                ret = -EIO;
+                break;
+            }
             src_buf = cluster_data;
         } else {
             src_buf = buf;
@@ -703,6 +748,7 @@ static void qcow_close(BlockDriverState *bs)
 {
     BDRVQcowState *s = bs->opaque;
 
+    qcrypto_cipher_free(s->cipher);
     g_free(s->l1_table);
     qemu_vfree(s->l2_cache);
     g_free(s->cluster_cache);
diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
index ed2b44d..afc2d90 100644
--- a/block/qcow2-cluster.c
+++ b/block/qcow2-cluster.c
@@ -342,26 +342,47 @@ static int count_contiguous_free_clusters(uint64_t nb_clusters, uint64_t *l2_tab
 /* The crypt function is compatible with the linux cryptoloop
    algorithm for < 4 GB images. NOTE: out_buf == in_buf is
    supported */
-void qcow2_encrypt_sectors(BDRVQcowState *s, int64_t sector_num,
-                           uint8_t *out_buf, const uint8_t *in_buf,
-                           int nb_sectors, int enc,
-                           const AES_KEY *key)
+int qcow2_encrypt_sectors(BDRVQcowState *s, int64_t sector_num,
+                          uint8_t *out_buf, const uint8_t *in_buf,
+                          int nb_sectors, bool enc,
+                          Error **errp)
 {
     union {
         uint64_t ll[2];
         uint8_t b[16];
     } ivec;
     int i;
+    int ret;
 
     for(i = 0; i < nb_sectors; i++) {
         ivec.ll[0] = cpu_to_le64(sector_num);
         ivec.ll[1] = 0;
-        AES_cbc_encrypt(in_buf, out_buf, 512, key,
-                        ivec.b, enc);
+        if (qcrypto_cipher_setiv(s->cipher,
+                                 ivec.b, G_N_ELEMENTS(ivec.b),
+                                 errp) < 0) {
+            return -1;
+        }
+        if (enc) {
+            ret = qcrypto_cipher_encrypt(s->cipher,
+                                         in_buf,
+                                         out_buf,
+                                         512,
+                                         errp);
+        } else {
+            ret = qcrypto_cipher_decrypt(s->cipher,
+                                         in_buf,
+                                         out_buf,
+                                         512,
+                                         errp);
+        }
+        if (ret < 0) {
+            return -1;
+        }
         sector_num++;
         in_buf += 512;
         out_buf += 512;
     }
+    return 0;
 }
 
 static int coroutine_fn copy_sectors(BlockDriverState *bs,
@@ -403,10 +424,15 @@ static int coroutine_fn copy_sectors(BlockDriverState *bs,
         goto out;
     }
 
-    if (s->crypt_method) {
-        qcow2_encrypt_sectors(s, start_sect + n_start,
-                        iov.iov_base, iov.iov_base, n, 1,
-                        &s->aes_encrypt_key);
+    if (s->cipher) {
+        Error *err = NULL;
+        if (qcow2_encrypt_sectors(s, start_sect + n_start,
+                                  iov.iov_base, iov.iov_base, n,
+                                  true, &err) < 0) {
+            ret = -EIO;
+            error_free(err);
+            goto out;
+        }
     }
 
     ret = qcow2_pre_write_overlap_check(bs, 0,
diff --git a/block/qcow2.c b/block/qcow2.c
index 824bf52..82cbdf5 100644
--- a/block/qcow2.c
+++ b/block/qcow2.c
@@ -694,6 +694,11 @@ static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
         ret = -EINVAL;
         goto fail;
     }
+    if (!qcrypto_cipher_supports(QCRYPTO_CIPHER_ALG_AES)) {
+        error_setg(errp, "AES cipher not available");
+        ret = -EINVAL;
+        goto fail;
+    }
     s->crypt_method_header = header.crypt_method;
     if (s->crypt_method_header) {
         bs->encrypted = 1;
@@ -1026,6 +1031,7 @@ static int qcow2_set_key(BlockDriverState *bs, const char *key)
     BDRVQcowState *s = bs->opaque;
     uint8_t keybuf[16];
     int len, i;
+    Error *err = NULL;
 
     memset(keybuf, 0, 16);
     len = strlen(key);
@@ -1036,30 +1042,20 @@ static int qcow2_set_key(BlockDriverState *bs, const char *key)
     for(i = 0;i < len;i++) {
         keybuf[i] = key[i];
     }
-    s->crypt_method = s->crypt_method_header;
 
-    if (AES_set_encrypt_key(keybuf, 128, &s->aes_encrypt_key) != 0)
-        return -1;
-    if (AES_set_decrypt_key(keybuf, 128, &s->aes_decrypt_key) != 0)
+    if (s->cipher) {
+        qcrypto_cipher_free(s->cipher);
+    }
+    s->cipher = qcrypto_cipher_new(
+        QCRYPTO_CIPHER_ALG_AES,
+        QCRYPTO_CIPHER_MODE_CBC,
+        keybuf, G_N_ELEMENTS(keybuf),
+        &err);
+
+    if (!s->cipher) {
+        error_free(err);
         return -1;
-#if 0
-    /* test */
-    {
-        uint8_t in[16];
-        uint8_t out[16];
-        uint8_t tmp[16];
-        for(i=0;i<16;i++)
-            in[i] = i;
-        AES_encrypt(in, tmp, &s->aes_encrypt_key);
-        AES_decrypt(tmp, out, &s->aes_decrypt_key);
-        for(i = 0; i < 16; i++)
-            printf(" %02x", tmp[i]);
-        printf("\n");
-        for(i = 0; i < 16; i++)
-            printf(" %02x", out[i]);
-        printf("\n");
     }
-#endif
     return 0;
 }
 
@@ -1102,7 +1098,7 @@ static int64_t coroutine_fn qcow2_co_get_block_status(BlockDriverState *bs,
     }
 
     if (cluster_offset != 0 && ret != QCOW2_CLUSTER_COMPRESSED &&
-        !s->crypt_method) {
+        !s->cipher) {
         index_in_cluster = sector_num & (s->cluster_sectors - 1);
         cluster_offset |= (index_in_cluster << BDRV_SECTOR_BITS);
         status |= BDRV_BLOCK_OFFSET_VALID | cluster_offset;
@@ -1152,7 +1148,7 @@ static coroutine_fn int qcow2_co_readv(BlockDriverState *bs, int64_t sector_num,
 
         /* prepare next request */
         cur_nr_sectors = remaining_sectors;
-        if (s->crypt_method) {
+        if (s->cipher) {
             cur_nr_sectors = MIN(cur_nr_sectors,
                 QCOW_MAX_CRYPT_CLUSTERS * s->cluster_sectors);
         }
@@ -1223,7 +1219,7 @@ static coroutine_fn int qcow2_co_readv(BlockDriverState *bs, int64_t sector_num,
                 goto fail;
             }
 
-            if (s->crypt_method) {
+            if (s->cipher) {
                 /*
                  * For encrypted images, read everything into a temporary
                  * contiguous buffer on which the AES functions can work.
@@ -1254,9 +1250,15 @@ static coroutine_fn int qcow2_co_readv(BlockDriverState *bs, int64_t sector_num,
             if (ret < 0) {
                 goto fail;
             }
-            if (s->crypt_method) {
-                qcow2_encrypt_sectors(s, sector_num,  cluster_data,
-                    cluster_data, cur_nr_sectors, 0, &s->aes_decrypt_key);
+            if (s->cipher) {
+                Error *err = NULL;
+                if (qcow2_encrypt_sectors(s, sector_num,  cluster_data,
+                                          cluster_data, cur_nr_sectors, false,
+                                          &err) < 0) {
+                    error_free(err);
+                    ret = -EIO;
+                    goto fail;
+                }
                 qemu_iovec_from_buf(qiov, bytes_done,
                     cluster_data, 512 * cur_nr_sectors);
             }
@@ -1314,7 +1316,7 @@ static coroutine_fn int qcow2_co_writev(BlockDriverState *bs,
         trace_qcow2_writev_start_part(qemu_coroutine_self());
         index_in_cluster = sector_num & (s->cluster_sectors - 1);
         cur_nr_sectors = remaining_sectors;
-        if (s->crypt_method &&
+        if (s->cipher &&
             cur_nr_sectors >
             QCOW_MAX_CRYPT_CLUSTERS * s->cluster_sectors - index_in_cluster) {
             cur_nr_sectors =
@@ -1333,7 +1335,8 @@ static coroutine_fn int qcow2_co_writev(BlockDriverState *bs,
         qemu_iovec_concat(&hd_qiov, qiov, bytes_done,
             cur_nr_sectors * 512);
 
-        if (s->crypt_method) {
+        if (s->cipher) {
+            Error *err = NULL;
             if (!cluster_data) {
                 cluster_data = qemu_try_blockalign(bs->file,
                                                    QCOW_MAX_CRYPT_CLUSTERS
@@ -1348,8 +1351,13 @@ static coroutine_fn int qcow2_co_writev(BlockDriverState *bs,
                    QCOW_MAX_CRYPT_CLUSTERS * s->cluster_size);
             qemu_iovec_to_buf(&hd_qiov, 0, cluster_data, hd_qiov.size);
 
-            qcow2_encrypt_sectors(s, sector_num, cluster_data,
-                cluster_data, cur_nr_sectors, 1, &s->aes_encrypt_key);
+            if (qcow2_encrypt_sectors(s, sector_num, cluster_data,
+                                      cluster_data, cur_nr_sectors,
+                                      true, &err) < 0) {
+                error_free(err);
+                ret = -EIO;
+                goto fail;
+            }
 
             qemu_iovec_reset(&hd_qiov);
             qemu_iovec_add(&hd_qiov, cluster_data,
@@ -1455,6 +1463,8 @@ static void qcow2_close(BlockDriverState *bs)
     qcow2_cache_destroy(bs, s->l2_table_cache);
     qcow2_cache_destroy(bs, s->refcount_block_cache);
 
+    qcrypto_cipher_free(s->cipher);
+
     g_free(s->unknown_header_fields);
     cleanup_unknown_header_ext(bs);
 
@@ -1471,9 +1481,7 @@ static void qcow2_invalidate_cache(BlockDriverState *bs, Error **errp)
 {
     BDRVQcowState *s = bs->opaque;
     int flags = s->flags;
-    AES_KEY aes_encrypt_key;
-    AES_KEY aes_decrypt_key;
-    uint32_t crypt_method = 0;
+    QCryptoCipher *cipher = NULL;
     QDict *options;
     Error *local_err = NULL;
     int ret;
@@ -1483,11 +1491,8 @@ static void qcow2_invalidate_cache(BlockDriverState *bs, Error **errp)
      * that means we don't have to worry about reopening them here.
      */
 
-    if (s->crypt_method) {
-        crypt_method = s->crypt_method;
-        memcpy(&aes_encrypt_key, &s->aes_encrypt_key, sizeof(aes_encrypt_key));
-        memcpy(&aes_decrypt_key, &s->aes_decrypt_key, sizeof(aes_decrypt_key));
-    }
+    cipher = s->cipher;
+    s->cipher = NULL;
 
     qcow2_close(bs);
 
@@ -1512,11 +1517,7 @@ static void qcow2_invalidate_cache(BlockDriverState *bs, Error **errp)
         return;
     }
 
-    if (crypt_method) {
-        s->crypt_method = crypt_method;
-        memcpy(&s->aes_encrypt_key, &aes_encrypt_key, sizeof(aes_encrypt_key));
-        memcpy(&s->aes_decrypt_key, &aes_decrypt_key, sizeof(aes_decrypt_key));
-    }
+    s->cipher = cipher;
 }
 
 static size_t header_ext_add(char *buf, uint32_t magic, const void *s,
@@ -2717,8 +2718,9 @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts,
             backing_format = qemu_opt_get(opts, BLOCK_OPT_BACKING_FMT);
         } else if (!strcmp(desc->name, BLOCK_OPT_ENCRYPT)) {
             encrypt = qemu_opt_get_bool(opts, BLOCK_OPT_ENCRYPT,
-                                        s->crypt_method);
-            if (encrypt != !!s->crypt_method) {
+                                        !!s->cipher);
+
+            if (encrypt != !!s->cipher) {
                 fprintf(stderr, "Changing the encryption flag is not "
                         "supported.\n");
                 return -ENOTSUP;
diff --git a/block/qcow2.h b/block/qcow2.h
index 3cf5318..4af1471 100644
--- a/block/qcow2.h
+++ b/block/qcow2.h
@@ -25,7 +25,7 @@
 #ifndef BLOCK_QCOW2_H
 #define BLOCK_QCOW2_H
 
-#include "crypto/aes.h"
+#include "crypto/cipher.h"
 #include "block/coroutine.h"
 
 //#define DEBUG_ALLOC
@@ -250,10 +250,8 @@ typedef struct BDRVQcowState {
 
     CoMutex lock;
 
-    uint32_t crypt_method; /* current crypt method, 0 if no key yet */
+    QCryptoCipher *cipher; /* current cipher, NULL if no key yet */
     uint32_t crypt_method_header;
-    AES_KEY aes_encrypt_key;
-    AES_KEY aes_decrypt_key;
     uint64_t snapshots_offset;
     int snapshots_size;
     unsigned int nb_snapshots;
@@ -533,10 +531,9 @@ int qcow2_grow_l1_table(BlockDriverState *bs, uint64_t min_size,
 int qcow2_write_l1_entry(BlockDriverState *bs, int l1_index);
 void qcow2_l2_cache_reset(BlockDriverState *bs);
 int qcow2_decompress_cluster(BlockDriverState *bs, uint64_t cluster_offset);
-void qcow2_encrypt_sectors(BDRVQcowState *s, int64_t sector_num,
-                     uint8_t *out_buf, const uint8_t *in_buf,
-                     int nb_sectors, int enc,
-                     const AES_KEY *key);
+int qcow2_encrypt_sectors(BDRVQcowState *s, int64_t sector_num,
+                          uint8_t *out_buf, const uint8_t *in_buf,
+                          int nb_sectors, bool enc, Error **errp);
 
 int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset,
     int *num, uint64_t *cluster_offset);
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 20/34] ui: convert VNC to use generic cipher API
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (18 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 19/34] block: convert qcow/qcow2 to use generic cipher API Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 21/34] io: add abstract QIOChannel classes Daniel P. Berrange
                   ` (14 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

Switch the VNC server over to use the generic cipher API, this
allows it to use the pluggable DES implementations, instead of
being hardcoded to use QEMU's built-in impl.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 ui/vnc.c | 52 +++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 41 insertions(+), 11 deletions(-)

diff --git a/ui/vnc.c b/ui/vnc.c
index ef0d87d..f55776e 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -47,7 +47,7 @@ static const struct timeval VNC_REFRESH_STATS = { 0, 500000 };
 static const struct timeval VNC_REFRESH_LOSSY = { 2, 0 };
 
 #include "vnc_keysym.h"
-#include "crypto/desrfb.h"
+#include "crypto/cipher.h"
 
 static QTAILQ_HEAD(, VncDisplay) vnc_displays =
     QTAILQ_HEAD_INITIALIZER(vnc_displays);
@@ -2515,9 +2515,11 @@ static void make_challenge(VncState *vs)
 static int protocol_client_auth_vnc(VncState *vs, uint8_t *data, size_t len)
 {
     unsigned char response[VNC_AUTH_CHALLENGE_SIZE];
-    int i, j, pwlen;
+    size_t i, pwlen;
     unsigned char key[8];
     time_t now = time(NULL);
+    QCryptoCipher *cipher;
+    Error *err;
 
     if (!vs->vd->password) {
         VNC_DEBUG("No password configured on server");
@@ -2534,9 +2536,29 @@ static int protocol_client_auth_vnc(VncState *vs, uint8_t *data, size_t len)
     pwlen = strlen(vs->vd->password);
     for (i=0; i<sizeof(key); i++)
         key[i] = i<pwlen ? vs->vd->password[i] : 0;
-    deskey(key, EN0);
-    for (j = 0; j < VNC_AUTH_CHALLENGE_SIZE; j += 8)
-        des(response+j, response+j);
+
+    cipher = qcrypto_cipher_new(
+        QCRYPTO_CIPHER_ALG_DES_RFB,
+        QCRYPTO_CIPHER_MODE_ECB,
+        key, G_N_ELEMENTS(key),
+        &err);
+    if (!cipher) {
+        VNC_DEBUG("Cannot initialize cipher %s",
+                  error_get_pretty(err));
+        error_free(err);
+        goto reject;
+    }
+
+    if (qcrypto_cipher_decrypt(cipher,
+                               vs->challenge,
+                               response,
+                               VNC_AUTH_CHALLENGE_SIZE,
+                               &err) < 0) {
+        VNC_DEBUG("Cannot encrypt challenge %s",
+                  error_get_pretty(err));
+        error_free(err);
+        goto reject;
+    }
 
     /* Compare expected vs actual challenge response */
     if (memcmp(response, data, VNC_AUTH_CHALLENGE_SIZE) != 0) {
@@ -3476,12 +3498,20 @@ void vnc_display_open(const char *id, Error **errp)
     }
 
     password = qemu_opt_get_bool(opts, "password", false);
-    if (password && fips_get_state()) {
-        error_setg(errp,
-                   "VNC password auth disabled due to FIPS mode, "
-                   "consider using the VeNCrypt or SASL authentication "
-                   "methods as an alternative");
-        goto fail;
+    if (password) {
+        if (fips_get_state()) {
+            error_setg(errp,
+                       "VNC password auth disabled due to FIPS mode, "
+                       "consider using the VeNCrypt or SASL authentication "
+                       "methods as an alternative");
+            goto fail;
+        }
+        if (!qcrypto_cipher_supports(
+                QCRYPTO_CIPHER_ALG_DES_RFB)) {
+            error_setg(errp,
+                       "Cipher backend does not support DES RFB algorithm");
+            goto fail;
+        }
     }
 
     reverse = qemu_opt_get_bool(opts, "reverse", false);
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 21/34] io: add abstract QIOChannel classes
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (19 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 20/34] ui: convert VNC " Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 22/34] io: add helper module for creating watches on UNIX FDs Daniel P. Berrange
                   ` (13 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

Start the new generic I/O channel framework by defining a
QIOChannel abstract base class. This is designed to feel
similar to GLib's GIOChannel, but with the addition of
support for using iovecs, qemu error reporting, file
descriptor passing and msg peeking.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 Makefile.objs        |   2 +-
 include/io/channel.h | 388 +++++++++++++++++++++++++++++++++++++++++++++++++++
 io/Makefile.objs     |   1 +
 io/channel.c         | 178 +++++++++++++++++++++++
 4 files changed, 568 insertions(+), 1 deletion(-)
 create mode 100644 include/io/channel.h
 create mode 100644 io/Makefile.objs
 create mode 100644 io/channel.c

diff --git a/Makefile.objs b/Makefile.objs
index 0f4dd84..a53ff44 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -2,7 +2,7 @@
 # Common libraries for tools and emulators
 stub-obj-y = stubs/
 util-obj-y = util/ qobject/ qapi/ qapi-types.o qapi-visit.o qapi-event.o
-util-obj-y += crypto/
+util-obj-y += crypto/ io/
 
 #######################################################################
 # block-obj-y is code used by both qemu system emulation and qemu-img
diff --git a/include/io/channel.h b/include/io/channel.h
new file mode 100644
index 0000000..5a18fff
--- /dev/null
+++ b/include/io/channel.h
@@ -0,0 +1,388 @@
+/*
+ * QEMU I/O channels
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QIO_CHANNEL_H__
+#define QIO_CHANNEL_H__
+
+#include "qemu-common.h"
+#include "qapi/error.h"
+#include "qom/object.h"
+
+#define TYPE_QIO_CHANNEL "qemu:io-channel"
+#define QIO_CHANNEL(obj)                                    \
+    OBJECT_CHECK(QIOChannel, (obj), TYPE_QIO_CHANNEL)
+#define QIO_CHANNEL_CLASS(klass)                                    \
+    OBJECT_CLASS_CHECK(QIOChannelClass, klass, TYPE_QIO_CHANNEL)
+#define QIO_CHANNEL_GET_CLASS(obj)                                  \
+    OBJECT_GET_CLASS(QIOChannelClass, obj, TYPE_QIO_CHANNEL)
+
+typedef struct QIOChannel QIOChannel;
+typedef struct QIOChannelClass QIOChannelClass;
+
+#define QIO_CHANNEL_ERR_BLOCK -2
+
+typedef enum {
+    QIO_CHANNEL_READ_PEEK = (1 << 0),
+} QIOChannelReadFlags;
+
+typedef enum {
+    QIO_CHANNEL_FEATURE_FD_PASS = (1 << 0),
+} QIOChannelFeature;
+
+typedef gboolean (*QIOChannelFunc)(QIOChannel *ioc,
+                                   GIOCondition condition,
+                                   gpointer data);
+
+/**
+ * QIOChannel:
+ *
+ * The QIOChannel defines the core API for a generic I/O channel
+ * class hierarchy. It is inspired by GIOChannel, but has the
+ * following differences
+ *
+ *  - Use QOM to properly support arbitrary subclassing
+ *  - Support use of iovecs for efficient I/O with multiple blocks
+ *  - None of the character set translation, binary data exclusively
+ *  - Direct support for QEMU Error object reporting
+ *  - File descriptor passing
+ *  - Peeking at incoming data without reading it
+ *
+ * This base class is abstract so cannot be instantiated. There
+ * will be subclasses for dealing with sockets, files, and higher
+ * level protocols such as TLS, WebSocket, etc.
+ */
+
+struct QIOChannel {
+    Object parent;
+};
+
+/**
+ * QIOChannelClass:
+ *
+ * This class defines the contract that all subclasses
+ * must follow to provide specific channel implementations.
+ * All the callbacks are mandatory with the exception of
+ * io_has_feature, which defaults to returning false.
+ *
+ * Consult the corresponding public API docs for a description
+ * of the semantics of each callback
+ */
+struct QIOChannelClass {
+    ObjectClass parent;
+
+    bool (*io_has_feature)(QIOChannel *ioc,
+                           QIOChannelFeature feature);
+    ssize_t (*io_writev)(QIOChannel *ioc,
+                         const struct iovec *iov,
+                         size_t niov,
+                         int *fds,
+                         size_t nfds,
+                         int flags,
+                         Error **errp);
+    ssize_t (*io_readv)(QIOChannel *ioc,
+                        const struct iovec *iov,
+                        size_t niov,
+                        int **fds,
+                        size_t *nfds,
+                        int flags,
+                        Error **errp);
+    int (*io_close)(QIOChannel *ioc,
+                    Error **errp);
+    GSource * (*io_create_watch)(QIOChannel *ioc,
+                                 GIOCondition condition);
+    void (*io_set_blocking)(QIOChannel *ioc,
+                            bool enabled);
+};
+
+/* General I/O handling functions */
+
+/**
+ * qio_channel_has_feature:
+ * @ioc: the channel object
+ * @feature: the feature to check support of
+ *
+ * Determine whether the channel implementation supports
+ * the optional feature named in @feature.
+ *
+ * Returns: true if supported, false otherwise.
+ */
+bool qio_channel_has_feature(QIOChannel *ioc,
+                             QIOChannelFeature feature);
+
+/**
+ * qio_channel_readv_full:
+ * @ioc: the channel object
+ * @iov: the array of memory regions to read data into
+ * @niov: the length of the @iov array
+ * @fds: pointer to an array that will received file handles
+ * @nfds: pointer filled with number of elements in @fds on return
+ * @flags: bitwise OR of QIOChannelReadFlags constants
+ * @errp: pointer to an uninitialized error object
+ *
+ * Read data from the IO channel, storing it in the
+ * memory regions referenced by @iov. Each element
+ * in the @iov will be fully populated with data
+ * before the next one is used. The @niov parameter
+ * specifies the total number of elements in @iov.
+ *
+ * It is not required for all @iov to be filled with
+ * data. If the channel is in blocking mode, at least
+ * one byte of data will be read, but no more is
+ * guaranteed. If the channel is non-blocking and no
+ * data is available, it will return QIO_CHANNEL_ERR_BLOCK
+ *
+ * If the channel has passed any file descriptors,
+ * the @fds array pointer will be allocated and
+ * the elements filled with the received file
+ * descriptors. The @nfds pointer will be updated
+ * to indicate the size of the @fds array that
+ * was allocated. It is the callers responsibility
+ * to call close() on each file descriptor and to
+ * call g_free() on the array pointer in @fds.
+ *
+ * It is an error to pass a non-NULL @fds parameter
+ * unless qio_channel_has_feature() returns a true
+ * value for the QIO_CHANNEL_FEATURE_FD_PASS constant.
+ *
+ * If the @flags parameter contains QIO_CHANNEL_READ_PEEK
+ * the data read will be left in the channel, available
+ * for a future read request.
+ *
+ * Returns: the number of bytes read, or -1 on error,
+ * or QIO_CHANNEL_ERR_BLOCK if no data is available
+ * and the channel is non-blocking
+ */
+ssize_t qio_channel_readv_full(QIOChannel *ioc,
+                               const struct iovec *iov,
+                               size_t niov,
+                               int **fds,
+                               size_t *nfds,
+                               int flags,
+                               Error **errp);
+
+
+/**
+ * qio_channel_writev_full:
+ * @ioc: the channel object
+ * @iov: the array of memory regions to write data from
+ * @niov: the length of the @iov array
+ * @fds: an array of file handles to send
+ * @nfds: number of file handles in @fds
+ * @flags: currently unused, pass 0.
+ * @errp: pointer to an uninitialized error object
+ *
+ * Write data to the IO channel, reading it from the
+ * memory regions referenced by @iov. Each element
+ * in the @iov will be fully sent, before the next
+ * one is used. The @niov parameter specifies the
+ * total number of elements in @iov.
+ *
+ * It is not required for all @iov data to be fully
+ * sent. If the channel is in blocking mode, at least
+ * one byte of data will be sent, but no more is
+ * guaranteed. If the channel is non-blocking and no
+ * data can be sent, it will return QIO_CHANNEL_ERR_BLOCK
+ *
+ * If there are file descriptors to send, the @fds
+ * array should be non-NULL and provide the handles.
+ * All file descriptors will be sent if at least one
+ * byte of data was sent.
+ *
+ * It is an error to pass a non-NULL @fds parameter
+ * unless qio_channel_has_feature() returns a true
+ * value for the QIO_CHANNEL_FEATURE_FD_PASS constant.
+ *
+ * The @flags parameter is currently unused and should
+ * be set to zero.
+ *
+ * Returns: the number of bytes sent, or -1 on error,
+ * or QIO_CHANNEL_ERR_BLOCK if no data is can be sent
+ * and the channel is non-blocking
+ */
+ssize_t qio_channel_writev_full(QIOChannel *ioc,
+                                const struct iovec *iov,
+                                size_t niov,
+                                int *fds,
+                                size_t nfds,
+                                int flags,
+                                Error **errp);
+
+/**
+ * qio_channel_readv:
+ * @ioc: the channel object
+ * @iov: the array of memory regions to read data into
+ * @niov: the length of the @iov array
+ * @errp: pointer to an uninitialized error object
+ *
+ * Behaves as qio_channel_readv_full() but does not support
+ * receiving of file handles, or setting of flags.
+ */
+ssize_t qio_channel_readv(QIOChannel *ioc,
+                          const struct iovec *iov,
+                          size_t niov,
+                          Error **errp);
+
+/**
+ * qio_channel_writev:
+ * @ioc: the channel object
+ * @iov: the array of memory regions to write data from
+ * @niov: the length of the @iov array
+ * @errp: pointer to an uninitialized error object
+ *
+ * Behaves as qio_channel_writev_full() but does not support
+ * sending of file handles, or setting of flags.
+ */
+ssize_t qio_channel_writev(QIOChannel *ioc,
+                           const struct iovec *iov,
+                           size_t niov,
+                           Error **errp);
+
+/**
+ * qio_channel_readv:
+ * @ioc: the channel object
+ * @buf: the memory region to read data into
+ * @buflen: the length of @buf
+ * @errp: pointer to an uninitialized error object
+ *
+ * Behaves as qio_channel_readv_full() but does not support
+ * receiving of file handles, or setting of flags, and
+ * only supports reading into a single memory region.
+ */
+ssize_t qio_channel_read(QIOChannel *ioc,
+                         char *buf,
+                         size_t buflen,
+                         Error **errp);
+
+/**
+ * qio_channel_writev:
+ * @ioc: the channel object
+ * @buf: the memory regions to send data from
+ * @buflen: the length of @buf
+ * @errp: pointer to an uninitialized error object
+ *
+ * Behaves as qio_channel_writev_full() but does not support
+ * sending of file handles, or setting of flags, and only
+ * supports writing from a single memory region.
+ */
+ssize_t qio_channel_write(QIOChannel *ioc,
+                          const char *buf,
+                          size_t buflen,
+                          Error **errp);
+
+/**
+ * qio_channel_set_blocking:
+ * @ioc: the channel object
+ * @enabled: the blocking flag state
+ *
+ * If @enabled is true, then the channel is put into
+ * blocking mode, otherwise it will be non-blocking.
+ *
+ * In non-blocking mode, read/write operations may
+ * return QIO_CHANNEL_ERR_BLOCK if they would otherwise
+ * block on I/O
+ */
+void qio_channel_set_blocking(QIOChannel *ioc,
+                              bool enabled);
+
+/**
+ * qio_channel_close:
+ * @ioc: the channel object
+ * @errp: pointer to an uninitialized error object
+ *
+ * Close the channel, flushing any pending I/O
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int qio_channel_close(QIOChannel *ioc,
+                      Error **errp);
+
+/**
+ * qio_channel_create_watch:
+ * @ioc: the channel object
+ * @condition: the I/O condition to monitor
+ *
+ * Create a new main loop source that is used to watch
+ * for the I/O condition @condition. Typically the
+ * qio_channel_add_watch() method would be used instead
+ * of this, since it directly attaches a callback to
+ * the source
+ *
+ * Returns: the new main loop source.
+ */
+GSource *qio_channel_create_watch(QIOChannel *ioc,
+                                  GIOCondition condition);
+
+/**
+ * qio_channel_add_watch_full:
+ * @ioc: the channel object
+ * @priority: the main loop priority
+ * @condition: the I/O condition to monitor
+ * @func: callback to invoke when the source becomes ready
+ * @user_data: opaque data to pass to @func
+ * @notify: callback to free @user_data
+ *
+ * Create a new main loop source that is used to watch
+ * for the I/O condition @condition. The callback @func
+ * will be registered against the source, to be invoked
+ * when the source becomes ready. The optional @user_data
+ * will be passed to @func when it is invoked. The @notify
+ * callback will be used to free @user_data when the
+ * watch is deleted
+ *
+ * The returned source ID can be used with g_source_remove()
+ * to remove and free the source when no longer required.
+ * Alternatively the @func callback can return a FALSE
+ * value.
+ *
+ * Returns: the source ID
+ */
+guint qio_channel_add_watch_full(QIOChannel *ioc,
+                                 gint priority,
+                                 GIOCondition condition,
+                                 QIOChannelFunc func,
+                                 gpointer user_data,
+                                 GDestroyNotify notify);
+
+/**
+ * qio_channel_add_watch_full:
+ * @ioc: the channel object
+ * @condition: the I/O condition to monitor
+ * @func: callback to invoke when the source becomes ready
+ * @user_data: opaque data to pass to @func
+ *
+ * Create a new main loop source that is used to watch
+ * for the I/O condition @condition. The callback @func
+ * will be registered against the source, to be invoked
+ * when the source becomes ready. The optional @user_data
+ * will be passed to @func when it is invoked.
+ *
+ * The returned source ID can be used with g_source_remove()
+ * to remove and free the source when no longer required.
+ * Alternatively the @func callback can return a FALSE
+ * value.
+ *
+ * Returns: the source ID
+ */
+guint qio_channel_add_watch(QIOChannel *ioc,
+                            GIOCondition condition,
+                            QIOChannelFunc func,
+                            gpointer user_data);
+
+#endif /* QIO_CHANNEL_H__ */
diff --git a/io/Makefile.objs b/io/Makefile.objs
new file mode 100644
index 0000000..ca088a8
--- /dev/null
+++ b/io/Makefile.objs
@@ -0,0 +1 @@
+util-obj-y += channel.o
diff --git a/io/channel.c b/io/channel.c
new file mode 100644
index 0000000..2d0f7d1
--- /dev/null
+++ b/io/channel.c
@@ -0,0 +1,178 @@
+/*
+ * QEMU I/O channels
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "io/channel.h"
+
+bool qio_channel_has_feature(QIOChannel *ioc,
+                             QIOChannelFeature feature)
+{
+    QIOChannelClass *klass = QIO_CHANNEL_GET_CLASS(ioc);
+
+    if (!klass->io_has_feature) {
+        return false;
+    }
+
+    return klass->io_has_feature(ioc, feature);
+}
+
+ssize_t qio_channel_readv_full(QIOChannel *ioc,
+                               const struct iovec *iov,
+                               size_t niov,
+                               int **fds,
+                               size_t *nfds,
+                               int flags,
+                               Error **errp)
+{
+    QIOChannelClass *klass = QIO_CHANNEL_GET_CLASS(ioc);
+
+    return klass->io_readv(ioc, iov, niov, fds, nfds, flags, errp);
+}
+
+ssize_t qio_channel_writev_full(QIOChannel *ioc,
+                                const struct iovec *iov,
+                                size_t niov,
+                                int *fds,
+                                size_t nfds,
+                                int flags,
+                                Error **errp)
+{
+    QIOChannelClass *klass = QIO_CHANNEL_GET_CLASS(ioc);
+    return klass->io_writev(ioc, iov, niov, fds, nfds, flags, errp);
+}
+
+
+ssize_t qio_channel_readv(QIOChannel *ioc,
+                          const struct iovec *iov,
+                          size_t niov,
+                          Error **errp)
+{
+    return qio_channel_readv_full(ioc, iov, niov, NULL, NULL, 0, errp);
+}
+
+ssize_t qio_channel_writev(QIOChannel *ioc,
+                           const struct iovec *iov,
+                           size_t niov,
+                           Error **errp)
+{
+    return qio_channel_writev_full(ioc, iov, niov, NULL, 0, 0, errp);
+}
+
+
+ssize_t qio_channel_read(QIOChannel *ioc,
+                         char *buf,
+                         size_t buflen,
+                         Error **errp)
+{
+    struct iovec iov = { .iov_base = buf, .iov_len = buflen };
+    return qio_channel_readv_full(ioc, &iov, 1, NULL, NULL, 0, errp);
+}
+
+ssize_t qio_channel_write(QIOChannel *ioc,
+                          const char *buf,
+                          size_t buflen,
+                          Error **errp)
+{
+    struct iovec iov = { .iov_base = (char *)buf, .iov_len = buflen };
+    return qio_channel_writev_full(ioc, &iov, 1, NULL, 0, 0, errp);
+}
+
+void qio_channel_set_blocking(QIOChannel *ioc,
+                              bool enabled)
+{
+    QIOChannelClass *klass = QIO_CHANNEL_GET_CLASS(ioc);
+    klass->io_set_blocking(ioc, enabled);
+}
+
+
+int qio_channel_close(QIOChannel *ioc,
+                      Error **errp)
+{
+    QIOChannelClass *klass = QIO_CHANNEL_GET_CLASS(ioc);
+    return klass->io_close(ioc, errp);
+}
+
+
+GSource *qio_channel_create_watch(QIOChannel *ioc,
+                                  GIOCondition condition)
+{
+    QIOChannelClass *klass = QIO_CHANNEL_GET_CLASS(ioc);
+    return klass->io_create_watch(ioc, condition);
+}
+
+
+guint qio_channel_add_watch_full(QIOChannel *ioc,
+                                 gint priority,
+                                 GIOCondition condition,
+                                 QIOChannelFunc func,
+                                 gpointer user_data,
+                                 GDestroyNotify notify)
+{
+    GSource *source;
+    guint id;
+
+    source = qio_channel_create_watch(ioc, condition);
+
+    if (priority != G_PRIORITY_DEFAULT) {
+        g_source_set_priority(source, priority);
+    }
+    g_source_set_callback(source, (GSourceFunc)func, user_data, notify);
+
+    id = g_source_attach(source, NULL);
+    g_source_unref(source);
+
+    return id;
+}
+
+guint qio_channel_add_watch(QIOChannel *ioc,
+                            GIOCondition condition,
+                            QIOChannelFunc func,
+                            gpointer user_data)
+{
+    return qio_channel_add_watch_full(ioc, G_PRIORITY_DEFAULT,
+                                      condition, func, user_data, NULL);
+}
+
+static void qio_channel_init(Object *obj G_GNUC_UNUSED)
+{
+    /* nada */
+}
+
+static void qio_channel_finalize(Object *obj G_GNUC_UNUSED)
+{
+    /* nada */
+}
+
+
+static const TypeInfo qio_channel_info = {
+    .parent = TYPE_OBJECT,
+    .name = TYPE_QIO_CHANNEL,
+    .instance_size = sizeof(QIOChannel),
+    .instance_init = qio_channel_init,
+    .instance_finalize = qio_channel_finalize,
+    .abstract = true,
+    .class_size = sizeof(QIOChannelClass),
+};
+
+static void qio_channel_register_types(void)
+{
+    type_register_static(&qio_channel_info);
+}
+
+type_init(qio_channel_register_types);
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 22/34] io: add helper module for creating watches on UNIX FDs
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (20 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 21/34] io: add abstract QIOChannel classes Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 23/34] io: add QIOChannelSocket class Daniel P. Berrange
                   ` (12 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

A number of the channel implementations will require the
ability to create watches on UNIX file descriptors. To
avoid duplicating this code in each channel, provide a
helper API for dealing with UNIX FDs. This code is
explicitly not portable to the Windows platform, which
will require its own set of helpers

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 include/io/channel-unix.h |  50 +++++++++++++++++++++++
 io/Makefile.objs          |   1 +
 io/channel-unix.c         | 100 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 151 insertions(+)
 create mode 100644 include/io/channel-unix.h
 create mode 100644 io/channel-unix.c

diff --git a/include/io/channel-unix.h b/include/io/channel-unix.h
new file mode 100644
index 0000000..b50aeaf
--- /dev/null
+++ b/include/io/channel-unix.h
@@ -0,0 +1,50 @@
+/*
+ * QEMU I/O channels UNIX platform helper APIs
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QIO_CHANNEL_UNIX_H__
+#define QIO_CHANNEL_UNIX_H__
+
+#include "io/channel.h"
+
+/*
+ * This module provides helper functions that will be needed by
+ * the various QIOChannel implementations, for performing UNIX
+ * platform specific tasks. In particular setting up UNIX file
+ * descriptors watches.
+ */
+
+/**
+ * @ioc: the channel object
+ * @fd: the UNIX file descriptor
+ * @condition: the I/O condition
+ *
+ * Create a new main loop source that is able to
+ * monitor the UNIX file descriptor @fd for the
+ * I/O conditions in @condition. This is able
+ * monitor block devices, character devices,
+ * sockets, pipes but not plain files.
+ *
+ * Returns: the new main loop source
+ */
+GSource *qio_channel_unix_create_fd_watch(QIOChannel *ioc,
+                                          int fd,
+                                          GIOCondition condition);
+
+#endif /* QIO_CHANNEL_UNIX_H__ */
diff --git a/io/Makefile.objs b/io/Makefile.objs
index ca088a8..a776676 100644
--- a/io/Makefile.objs
+++ b/io/Makefile.objs
@@ -1 +1,2 @@
 util-obj-y += channel.o
+util-obj-y += channel-unix.o
diff --git a/io/channel-unix.c b/io/channel-unix.c
new file mode 100644
index 0000000..2da08a12
--- /dev/null
+++ b/io/channel-unix.c
@@ -0,0 +1,100 @@
+/*
+ * QEMU I/O channels UNIX platform helper APIs
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "io/channel-unix.h"
+
+typedef struct QIOChannelUNIXSource QIOChannelUNIXSource;
+struct QIOChannelUNIXSource {
+    GSource parent;
+    GPollFD fd;
+    QIOChannel *ioc;
+    GIOCondition condition;
+};
+
+static gboolean
+qio_channel_unix_source_prepare(GSource *source G_GNUC_UNUSED,
+                                gint *timeout)
+{
+    *timeout = -1;
+
+    return FALSE;
+}
+
+static gboolean
+qio_channel_unix_source_check(GSource *source)
+{
+    QIOChannelUNIXSource *ssource = (QIOChannelUNIXSource *)source;
+    GIOCondition poll_condition = ssource->fd.revents;
+
+    return poll_condition & ssource->condition;
+}
+
+static gboolean
+qio_channel_unix_source_dispatch(GSource *source,
+                                 GSourceFunc callback,
+                                 gpointer user_data)
+{
+    QIOChannelFunc func = (QIOChannelFunc)callback;
+    QIOChannelUNIXSource *ssource = (QIOChannelUNIXSource *)source;
+
+    return (*func)(ssource->ioc,
+                   ssource->fd.revents & ssource->condition,
+                   user_data);
+}
+
+static void
+qio_channel_unix_source_finalize(GSource *source)
+{
+    QIOChannelUNIXSource *ssource = (QIOChannelUNIXSource *)source;
+
+    object_unref(OBJECT(ssource->ioc));
+}
+
+GSourceFuncs qio_channel_unix_source_funcs = {
+    qio_channel_unix_source_prepare,
+    qio_channel_unix_source_check,
+    qio_channel_unix_source_dispatch,
+    qio_channel_unix_source_finalize
+};
+
+GSource *qio_channel_unix_create_fd_watch(QIOChannel *ioc,
+                                          int fd,
+                                          GIOCondition condition)
+{
+    GSource *source;
+    QIOChannelUNIXSource *ssource;
+
+    source = g_source_new(&qio_channel_unix_source_funcs,
+                          sizeof(QIOChannelUNIXSource));
+    g_source_set_name(source, "QIOChannelUNIX");
+    ssource = (QIOChannelUNIXSource *)source;
+
+    ssource->ioc = ioc;
+    object_ref(OBJECT(ioc));
+
+    ssource->condition = condition;
+
+    ssource->fd.fd = fd;
+    ssource->fd.events = condition;
+
+    g_source_add_poll(source, &ssource->fd);
+
+    return source;
+}
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 23/34] io: add QIOChannelSocket class
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (21 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 22/34] io: add helper module for creating watches on UNIX FDs Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 15:28   ` Paolo Bonzini
  2015-04-20  7:18   ` Gerd Hoffmann
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 24/34] io: add QIOChannelFile class Daniel P. Berrange
                   ` (11 subsequent siblings)
  34 siblings, 2 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

Implement a QIOChannel subclass that supports sockets I/O

TBD check errno handling of windows port & fix watch impl

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 include/io/channel-socket.h | 168 +++++++++++++
 io/Makefile.objs            |   1 +
 io/channel-socket.c         | 572 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 741 insertions(+)
 create mode 100644 include/io/channel-socket.h
 create mode 100644 io/channel-socket.c

diff --git a/include/io/channel-socket.h b/include/io/channel-socket.h
new file mode 100644
index 0000000..b95349b
--- /dev/null
+++ b/include/io/channel-socket.h
@@ -0,0 +1,168 @@
+/*
+ * QEMU I/O channels sockets driver
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QIO_CHANNEL_SOCKET_H__
+#define QIO_CHANNEL_SOCKET_H__
+
+#include "io/channel.h"
+#include "qemu/sockets.h"
+
+#define TYPE_QIO_CHANNEL_SOCKET "qemu:io-channel-socket"
+#define QIO_CHANNEL_SOCKET(obj)                                     \
+    OBJECT_CHECK(QIOChannelSocket, (obj), TYPE_QIO_CHANNEL_SOCKET)
+
+typedef struct QIOChannelSocket QIOChannelSocket;
+
+/**
+ * QIOChannelSocket:
+ *
+ * The QIOChannelSocket class provides a channel implementation
+ * that can transport data over a UNIX socket or TCP socket.
+ * Beyond the core channel API, it also provides functionality
+ * for accepting client connections, tuning some socket
+ * parameters and getting socket address strings.
+ */
+
+struct QIOChannelSocket {
+    QIOChannel parent;
+    int fd;
+    struct sockaddr_storage localAddr;
+    socklen_t localAddrLen;
+    struct sockaddr_storage remoteAddr;
+    socklen_t remoteAddrLen;
+};
+
+
+/**
+ * qio_channel_socket_new_fd:
+ * @fd: the socket file descriptor
+ * @errp: pointer to an uninitialized error object
+ *
+ * Create a channel for performing I/O on the socket
+ * connection represented by the file descriptor @fd.
+ *
+ * Returns: the socket channel object, or NULL on error
+ */
+QIOChannelSocket *
+qio_channel_socket_new_fd(int fd,
+                          Error **errp);
+
+/**
+ * qio_channel_socket_get_local_addr_string:
+ * @ioc: the socket channel object
+ * @hostname: pointer to be filled with hostname string
+ * @servicename: pointer to be filled with servicename string
+ * @family: pointer to be filled with network address family
+ * @errp: pointer to an uninitialized error object
+ *
+ * Get the string representation of the local socket
+ * address. The address information will be stored in
+ * the @hostname, @servicename and @family parameters.
+ * The @hostname and @servicename strings will be
+ * allocated to the size required and should be free
+ * with g_free() when no longer required
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int
+qio_channel_socket_get_local_addr_string(QIOChannelSocket *ioc,
+                                         char **hostname,
+                                         char **servicename,
+                                         NetworkAddressFamily *family,
+                                         Error **errp);
+
+/**
+ * qio_channel_socket_get_remote_addr_string:
+ * @ioc: the socket channel object
+ * @hostname: pointer to be filled with hostname string
+ * @servicename: pointer to be filled with servicename string
+ * @family: pointer to be filled with network address family
+ * @errp: pointer to an uninitialized error object
+ *
+ * Get the string representation of the remote socket
+ * address. The address information will be stored in
+ * the @hostname, @servicename and @family parameters.
+ * The @hostname and @servicename strings will be
+ * allocated to the size required and should be free
+ * with g_free() when no longer required
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int
+qio_channel_socket_get_remote_addr_string(QIOChannelSocket *ioc,
+                                          char **hostname,
+                                          char **servicename,
+                                          NetworkAddressFamily *family,
+                                          Error **errp);
+
+/**
+ * qio_channel_socket_set_nodelay:
+ * @ioc: the socket channel object
+ * @enabled: the new flag state
+ *
+ * Set the state of the NODELAY socket flag. If the
+ * @enabled parameter is true, then NODELAY will be
+ * set and data will be transmitted immediately. If
+ * @enabled is false, then data may be temporarily
+ * held for transmission to enable writes to be
+ * coallesced.
+ */
+void
+qio_channel_socket_set_nodelay(QIOChannelSocket *ioc,
+                               bool enabled);
+
+/**
+ * qio_channel_socket_accept:
+ * @ioc: the socket channel object
+ * @errp: pointer to an uninitialized error object
+ *
+ * If the socket represents a server, then this accepts
+ * a new client connection. The returned channel will
+ * represent the connected client socket.
+ *
+ * Returns: the new client channel, or NULL on error
+ */
+QIOChannelSocket *
+qio_channel_socket_accept(QIOChannelSocket *ioc,
+                          Error **errp);
+
+typedef enum {
+    QIO_CHANNEL_SOCKET_SHUTDOWN_BOTH,
+    QIO_CHANNEL_SOCKET_SHUTDOWN_READ,
+    QIO_CHANNEL_SOCKET_SHUTDOWN_WRITE,
+} QIOChannelSocketShutdown;
+
+/**
+ * qio_channel_socket_shutdown:
+ * @ioc: the socket channel object
+ * @how: the direction to shutdown
+ * @errp: pointer to an uninitialized error object
+ *
+ * Shutdowns transmission or receiving on a socket
+ * without closing the socket file descriptor.
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int
+qio_channel_socket_shutdown(QIOChannelSocket *ioc,
+                            QIOChannelSocketShutdown how,
+                            Error **errp);
+
+#endif /* QIO_CHANNEL_SOCKET_H__ */
diff --git a/io/Makefile.objs b/io/Makefile.objs
index a776676..4f5e276 100644
--- a/io/Makefile.objs
+++ b/io/Makefile.objs
@@ -1,2 +1,3 @@
 util-obj-y += channel.o
 util-obj-y += channel-unix.o
+util-obj-y += channel-socket.o
diff --git a/io/channel-socket.c b/io/channel-socket.c
new file mode 100644
index 0000000..5eacf38
--- /dev/null
+++ b/io/channel-socket.c
@@ -0,0 +1,572 @@
+/*
+ * QEMU I/O channels sockets driver
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <glib/gi18n.h>
+
+#include "io/channel-socket.h"
+#include "io/channel-unix.h"
+
+#define SOCKET_MAX_FDS 16
+
+static int
+qio_channel_socket_get_addr_string(struct sockaddr_storage *sa,
+                                   socklen_t salen,
+                                   char **hostname,
+                                   char **servicename,
+                                   NetworkAddressFamily *family,
+                                   Error **errp)
+{
+    char host[NI_MAXHOST];
+    char serv[NI_MAXSERV];
+    int ret;
+
+    ret = getnameinfo((struct sockaddr *)sa, salen,
+                      host, sizeof(host),
+                      serv, sizeof(serv),
+                      NI_NUMERICHOST | NI_NUMERICSERV);
+    if (ret != 0) {
+        error_setg(errp, "Cannot format numeric socket address: %s\n",
+                   gai_strerror(ret));
+        return -1;
+    }
+
+    *hostname = g_strdup(host);
+    *servicename = g_strdup(serv);
+    *family = inet_netfamily(sa->ss_family);
+    return 0;
+}
+
+int
+qio_channel_socket_get_local_addr_string(QIOChannelSocket *ioc,
+                                         char **hostname,
+                                         char **servicename,
+                                         NetworkAddressFamily *family,
+                                         Error **errp)
+{
+    return qio_channel_socket_get_addr_string(&ioc->localAddr,
+                                              ioc->localAddrLen,
+                                              hostname,
+                                              servicename,
+                                              family,
+                                              errp);
+}
+
+int
+qio_channel_socket_get_remote_addr_string(QIOChannelSocket *ioc,
+                                          char **hostname,
+                                          char **servicename,
+                                          NetworkAddressFamily *family,
+                                          Error **errp)
+{
+    return qio_channel_socket_get_addr_string(&ioc->remoteAddr,
+                                              ioc->remoteAddrLen,
+                                              hostname,
+                                              servicename,
+                                              family,
+                                              errp);
+}
+
+QIOChannelSocket *
+qio_channel_socket_new_fd(int fd,
+                          Error **errp)
+{
+    QIOChannelSocket *ioc;
+
+    ioc = QIO_CHANNEL_SOCKET(object_new(TYPE_QIO_CHANNEL_SOCKET));
+
+    ioc->fd = fd;
+    ioc->remoteAddrLen = sizeof(ioc->remoteAddr);
+    ioc->localAddrLen = sizeof(ioc->localAddr);
+
+    if (getpeername(fd, (struct sockaddr *)&ioc->remoteAddr,
+                    &ioc->remoteAddrLen) < 0) {
+        if (socket_error() == ENOTCONN) {
+            memset(&ioc->remoteAddr, 0, sizeof(ioc->remoteAddr));
+            ioc->remoteAddrLen = sizeof(ioc->remoteAddr);
+        } else {
+            error_setg_errno(errp, socket_error(), "%s",
+                             _("Unable to query remote socket address"));
+            goto error;
+        }
+    }
+
+    if (getsockname(fd, (struct sockaddr *)&ioc->localAddr,
+                    &ioc->localAddrLen) < 0) {
+        error_setg_errno(errp, socket_error(), "%s",
+                         _("Unable to query local socket address"));
+        goto error;
+    }
+
+    return ioc;
+
+ error:
+    ioc->fd = -1; /* Let the caller close FD on failure */
+    object_unref(OBJECT(ioc));
+    return NULL;
+}
+
+QIOChannelSocket *
+qio_channel_socket_accept(QIOChannelSocket *ioc,
+                          Error **errp)
+{
+    QIOChannelSocket *cioc;
+
+    cioc = QIO_CHANNEL_SOCKET(object_new(TYPE_QIO_CHANNEL_SOCKET));
+    cioc->fd = -1;
+    cioc->remoteAddrLen = sizeof(ioc->remoteAddr);
+    cioc->localAddrLen = sizeof(ioc->localAddr);
+
+ retry:
+    cioc->fd = accept(ioc->fd, (struct sockaddr *)&cioc->remoteAddr,
+                      &cioc->remoteAddrLen);
+    if (cioc->fd < 0) {
+        if (socket_error() == EINTR) {
+            goto retry;
+        }
+        goto error;
+    }
+
+    if (getsockname(cioc->fd, (struct sockaddr *)&ioc->localAddr,
+                    &ioc->localAddrLen) < 0) {
+        error_setg_errno(errp, socket_error(), "%s",
+                         _("Unable to query local socket address"));
+        goto error;
+    }
+
+    return cioc;
+
+ error:
+    object_unref(OBJECT(cioc));
+    return NULL;
+}
+
+static void qio_channel_socket_init(Object *obj)
+{
+    QIOChannelSocket *ioc = QIO_CHANNEL_SOCKET(obj);
+    ioc->fd = -1;
+}
+
+static void qio_channel_socket_finalize(Object *obj)
+{
+    QIOChannelSocket *ioc = QIO_CHANNEL_SOCKET(obj);
+    if (ioc->fd != -1) {
+        close(ioc->fd);
+        ioc->fd = -1;
+    }
+}
+
+static bool qio_channel_socket_has_feature(QIOChannel *ioc,
+                                           QIOChannelFeature feature)
+{
+    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
+
+    switch (feature) {
+    case QIO_CHANNEL_FEATURE_FD_PASS:
+#ifndef WIN32
+        if (sioc->localAddr.ss_family == AF_UNIX) {
+            return true;
+        } else {
+#endif /* WIN32 */
+            return false;
+#ifndef WIN32
+        }
+#endif /* WIN32 */
+    default:
+        return false;
+    }
+}
+
+
+#ifndef WIN32
+static void qio_channel_socket_copy_fds(struct msghdr *msg,
+                                        int **fds, size_t *nfds)
+{
+    struct cmsghdr *cmsg;
+
+    *nfds = 0;
+    *fds = NULL;
+
+    for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+        int fd_size, i;
+        int gotfds;
+
+        if (cmsg->cmsg_len < CMSG_LEN(sizeof(int)) ||
+            cmsg->cmsg_level != SOL_SOCKET ||
+            cmsg->cmsg_type != SCM_RIGHTS) {
+            continue;
+        }
+
+        fd_size = cmsg->cmsg_len - CMSG_LEN(0);
+
+        if (!fd_size) {
+            continue;
+        }
+
+        gotfds = fd_size / sizeof(int);
+        *fds = g_renew(int, *fds, *nfds + gotfds);
+        memcpy(*fds + *nfds, CMSG_DATA(cmsg), fd_size);
+
+        for (i = 0; i < gotfds; i++) {
+            int fd = (*fds)[*nfds + i];
+            if (fd < 0) {
+                continue;
+            }
+
+            /* O_NONBLOCK is preserved across SCM_RIGHTS so reset it */
+            qemu_set_block(fd);
+
+#ifndef MSG_CMSG_CLOEXEC
+            qemu_set_cloexec(fd);
+#endif
+        }
+        *nfds += gotfds;
+    }
+}
+
+
+static ssize_t qio_channel_socket_readv(QIOChannel *ioc,
+                                        const struct iovec *iov,
+                                        size_t niov,
+                                        int **fds,
+                                        size_t *nfds,
+                                        int flags,
+                                        Error **errp)
+{
+    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
+    ssize_t ret;
+    struct msghdr msg = { NULL, };
+    char control[CMSG_SPACE(sizeof(int) * SOCKET_MAX_FDS)];
+    int sflags = 0;
+
+    if (flags & ~QIO_CHANNEL_READ_PEEK) {
+        error_setg_errno(errp, EINVAL,
+                         _("Flags %x are not supported"),
+                         flags & ~QIO_CHANNEL_READ_PEEK);
+        return -1;
+    }
+
+#ifdef MSG_CMSG_CLOEXEC
+    sflags |= MSG_CMSG_CLOEXEC;
+#endif
+    if (flags & QIO_CHANNEL_READ_PEEK) {
+        sflags |= MSG_PEEK;
+    }
+
+    msg.msg_iov = (struct iovec *)iov;
+    msg.msg_iovlen = niov;
+    if (fds && nfds) {
+        msg.msg_control = control;
+        msg.msg_controllen = sizeof(control);
+    }
+
+ retry:
+    ret = recvmsg(sioc->fd, &msg, sflags);
+    if (ret < 0) {
+        if (socket_error() == EAGAIN) {
+            return -2;
+        }
+        if (socket_error() == EINTR) {
+            goto retry;
+        }
+
+        error_setg_errno(errp, socket_error(), "%s",
+                         _("Unable to read from socket"));
+        return -1;
+    }
+
+    if (fds && nfds) {
+        qio_channel_socket_copy_fds(&msg, fds, nfds);
+    }
+
+    return ret;
+}
+
+static ssize_t qio_channel_socket_writev(QIOChannel *ioc,
+                                         const struct iovec *iov,
+                                         size_t niov,
+                                         int *fds,
+                                         size_t nfds,
+                                         int flags G_GNUC_UNUSED,
+                                         Error **errp)
+{
+    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
+    ssize_t ret;
+    struct msghdr msg = { NULL, };
+
+    if (flags) {
+        error_setg_errno(errp, EINVAL,
+                         _("Flags %x are not supported"),
+                         flags);
+        return -1;
+    }
+
+    msg.msg_iov = (struct iovec *)iov;
+    msg.msg_iovlen = niov;
+
+    if (nfds) {
+        char control[CMSG_SPACE(sizeof(int) * SOCKET_MAX_FDS)];
+        size_t fdsize = sizeof(int) * nfds;
+        struct cmsghdr *cmsg;
+
+        if (nfds > SOCKET_MAX_FDS) {
+            error_setg_errno(errp, -EINVAL,
+                             _("Only %d FDs can be sent, got %zu"),
+                             SOCKET_MAX_FDS, nfds);
+            return -1;
+        }
+
+        msg.msg_control = control;
+        msg.msg_controllen = CMSG_SPACE(sizeof(int) * nfds);
+
+        cmsg = CMSG_FIRSTHDR(&msg);
+        cmsg->cmsg_len = CMSG_LEN(fdsize);
+        cmsg->cmsg_level = SOL_SOCKET;
+        cmsg->cmsg_type = SCM_RIGHTS;
+        memcpy(CMSG_DATA(cmsg), fds, fdsize);
+    }
+
+ retry:
+    ret = sendmsg(sioc->fd, &msg, 0);
+    if (ret <= 0) {
+        if (socket_error() == EAGAIN) {
+            return -2;
+        }
+        if (socket_error() == EINTR) {
+            goto retry;
+        }
+        error_setg_errno(errp, socket_error(), "%s",
+                         _("Unable to write to socket"));
+        return -1;
+    }
+    return ret;
+}
+#else /* WIN32 */
+static ssize_t qio_channel_socket_readv(QIOChannel *ioc,
+                                        const struct iovec *iov,
+                                        size_t niov,
+                                        int **fds,
+                                        size_t *nfds,
+                                        int flags,
+                                        Error **errp)
+{
+    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
+    ssize_t done = 0;
+    ssize_t i;
+
+    if (flags) {
+        error_setg_errno(errp, EINVAL,
+                         _("Flags %x are not supported"),
+                         flags);
+        return -1;
+    }
+    if (fds || nfds) {
+        error_setg_errno(errp, EINVAL, "%s",
+                         _("Channel does not support file descriptor passing"));
+        return -1;
+    }
+
+    for (i = 0; i < niov; i++) {
+        ssize_t ret;
+    retry:
+        ret = recv(sioc->fd,
+                   iov[i].iov_base,
+                   iov[i].iov_len,
+                   0);
+        if (ret < 0) {
+            if (socket_error() == EAGAIN) {
+                if (done) {
+                    return done;
+                } else {
+                    return QIO_CHANNEL_ERR_BLOCK;
+                }
+            } else if (socket_error() == EINTR) {
+                goto retry;
+            } else {
+                error_setg_errno(errp, socket_error(), "%s",
+                                 _("Unable to write to socket"));
+                return -1;
+            }
+        }
+        done += ret;
+        if (ret < iov[i].iov_len) {
+            return done;
+        }
+    }
+
+    return done;
+}
+
+static ssize_t qio_channel_socket_writev(QIOChannel *ioc,
+                                         const struct iovec *iov,
+                                         size_t niov,
+                                         int *fds,
+                                         size_t nfds,
+                                         int flags G_GNUC_UNUSED,
+                                         Error **errp)
+{
+    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
+    ssize_t done = 0;
+    ssize_t i;
+
+    if (flags) {
+        error_setg_errno(errp, EINVAL,
+                         _("Flags %x are not supported"),
+                         flags);
+        return -1;
+    }
+    if (fds || nfds) {
+        error_setg_errno(errp, EINVAL, "%s",
+                         _("Channel does not support file descriptor passing"));
+        return -1;
+    }
+
+    for (i = 0; i < niov; i++) {
+        ssize_t ret;
+    retry:
+        ret = send(sioc->fd,
+                   iov[i].iov_base,
+                   iov[i].iov_len,
+                   0);
+        if (ret < 0) {
+            if (socket_error() == EAGAIN) {
+                if (done) {
+                    return done;
+                } else {
+                    return QIO_CHANNEL_ERR_BLOCK;
+                }
+            } else if (socket_error() == EINTR) {
+                goto retry;
+            } else {
+                error_setg_errno(errp, socket_error(), "%s",
+                                 _("Unable to write to socket"));
+                return -1;
+            }
+        }
+        done += ret;
+        if (ret < iov[i].iov_len) {
+            return done;
+        }
+    }
+
+    return done;
+}
+#endif /* WIN32 */
+
+static void qio_channel_socket_set_blocking(QIOChannel *ioc,
+                                            bool enabled)
+{
+    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
+
+    if (enabled) {
+        qemu_set_block(sioc->fd);
+    } else {
+        qemu_set_nonblock(sioc->fd);
+    }
+}
+
+void
+qio_channel_socket_set_nodelay(QIOChannelSocket *ioc,
+                               bool enabled)
+{
+    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
+    int v = enabled ? 1 : 0;
+
+    qemu_setsockopt(sioc->fd, IPPROTO_TCP, TCP_NODELAY, &v, sizeof(v));
+}
+
+static int qio_channel_socket_close(QIOChannel *ioc,
+                                    Error **errp)
+{
+    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
+
+    if (closesocket(sioc->fd) < 0) {
+        sioc->fd = -1;
+        error_setg_errno(errp, socket_error(), "%s",
+                         _("Unable to close socket"));
+        return -1;
+    }
+    sioc->fd = -1;
+    return 0;
+}
+
+int
+qio_channel_socket_shutdown(QIOChannelSocket *ioc,
+                            QIOChannelSocketShutdown how,
+                            Error **errp)
+{
+    int sockhow;
+    switch (how) {
+    case QIO_CHANNEL_SOCKET_SHUTDOWN_READ:
+        sockhow = SHUT_RD;
+        break;
+    case QIO_CHANNEL_SOCKET_SHUTDOWN_WRITE:
+        sockhow = SHUT_WR;
+        break;
+    case QIO_CHANNEL_SOCKET_SHUTDOWN_BOTH:
+    default:
+        sockhow = SHUT_RDWR;
+        break;
+    }
+
+    if (shutdown(ioc->fd, sockhow) < 0) {
+        error_setg_errno(errp, socket_error(), "%s",
+                         _("Unable to shutdown socket"));
+        return -1;
+    }
+    return 0;
+}
+
+static GSource *qio_channel_socket_create_watch(QIOChannel *ioc,
+                                                GIOCondition condition)
+{
+    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
+    return qio_channel_unix_create_fd_watch(ioc,
+                                            sioc->fd,
+                                            condition);
+}
+
+static void qio_channel_socket_class_init(ObjectClass *klass,
+                                          void *class_data G_GNUC_UNUSED)
+{
+    QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass);
+
+    ioc_klass->io_has_feature = qio_channel_socket_has_feature;
+    ioc_klass->io_writev = qio_channel_socket_writev;
+    ioc_klass->io_readv = qio_channel_socket_readv;
+    ioc_klass->io_set_blocking = qio_channel_socket_set_blocking;
+    ioc_klass->io_close = qio_channel_socket_close;
+    ioc_klass->io_create_watch = qio_channel_socket_create_watch;
+}
+
+static const TypeInfo qio_channel_socket_info = {
+    .parent = TYPE_QIO_CHANNEL,
+    .name = TYPE_QIO_CHANNEL_SOCKET,
+    .instance_size = sizeof(QIOChannelSocket),
+    .instance_init = qio_channel_socket_init,
+    .instance_finalize = qio_channel_socket_finalize,
+    .class_init = qio_channel_socket_class_init,
+};
+
+static void qio_channel_socket_register_types(void)
+{
+    type_register_static(&qio_channel_socket_info);
+}
+
+type_init(qio_channel_socket_register_types);
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 24/34] io: add QIOChannelFile class
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (22 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 23/34] io: add QIOChannelSocket class Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 25/34] io: add QIOTask class for async operations Daniel P. Berrange
                   ` (10 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

Add a QIOChannel subclass that is capable of operating on things
that are files, such as plain files, pipes, character/block
devices, but notably not sockets.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 include/io/channel-file.h |  67 ++++++++++++++++
 io/Makefile.objs          |   1 +
 io/channel-file.c         | 198 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 266 insertions(+)
 create mode 100644 include/io/channel-file.h
 create mode 100644 io/channel-file.c

diff --git a/include/io/channel-file.h b/include/io/channel-file.h
new file mode 100644
index 0000000..6bbbe5d
--- /dev/null
+++ b/include/io/channel-file.h
@@ -0,0 +1,67 @@
+/*
+ * QEMU I/O channels files driver
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QIO_CHANNEL_FILE_H__
+#define QIO_CHANNEL_FILE_H__
+
+#include "io/channel.h"
+
+#define TYPE_QIO_CHANNEL_FILE "qemu:io-channel-file"
+#define QIO_CHANNEL_FILE(obj)                                     \
+    OBJECT_CHECK(QIOChannelFile, (obj), TYPE_QIO_CHANNEL_FILE)
+
+typedef struct _QIOChannelFile QIOChannelFile;
+
+/**
+ * QIOChannelFile:
+ *
+ * The QIOChannelFile object provides a channel implementation
+ * that is able to perform I/O on block devices, character
+ * devices, FIFOs, pipes and plain files. While it is technically
+ * able to work on sockets too on the UNIX platform, this is not
+ * portable to Windows and lacks some extra sockets specific
+ * functionality. So the QIOChannelSocket object is recommended
+ * for that use case.
+ *
+ */
+
+struct _QIOChannelFile {
+    QIOChannel parent;
+    int fd;
+};
+
+
+/**
+ * qio_channel_file_new_fd:
+ * @fd: the file descriptor
+ *
+ * Create a new IO channel object for a file represented
+ * by the @fd parameter. @fd can be associated with a
+ * block device, character device, fifo, pipe, or a
+ * regular file. For sockets, the QIOChannelSocket class
+ * should be used instead, as this provides greater
+ * functionality and cross platform portability.
+ *
+ * Returns: the new channel object
+ */
+QIOChannelFile *
+qio_channel_file_new_fd(int fd);
+
+#endif /* QIO_CHANNEL_FILE_H__ */
diff --git a/io/Makefile.objs b/io/Makefile.objs
index 4f5e276..eec1b43 100644
--- a/io/Makefile.objs
+++ b/io/Makefile.objs
@@ -1,3 +1,4 @@
 util-obj-y += channel.o
 util-obj-y += channel-unix.o
 util-obj-y += channel-socket.o
+util-obj-y += channel-file.o
diff --git a/io/channel-file.c b/io/channel-file.c
new file mode 100644
index 0000000..4da3ce1
--- /dev/null
+++ b/io/channel-file.c
@@ -0,0 +1,198 @@
+/*
+ * QEMU I/O channels files driver
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <glib/gi18n.h>
+
+#include "io/channel-file.h"
+#include "io/channel-unix.h"
+#include "qemu/sockets.h"
+
+QIOChannelFile *
+qio_channel_file_new_fd(int fd)
+{
+    QIOChannelFile *ioc;
+
+    ioc = QIO_CHANNEL_FILE(object_new(TYPE_QIO_CHANNEL_FILE));
+
+    ioc->fd = fd;
+
+    return ioc;
+}
+
+
+static void qio_channel_file_init(Object *obj)
+{
+    QIOChannelFile *ioc = QIO_CHANNEL_FILE(obj);
+    ioc->fd = -1;
+}
+
+static void qio_channel_file_finalize(Object *obj)
+{
+    QIOChannelFile *ioc = QIO_CHANNEL_FILE(obj);
+    if (ioc->fd != -1) {
+        close(ioc->fd);
+        ioc->fd = -1;
+    }
+}
+
+
+static ssize_t qio_channel_file_readv(QIOChannel *ioc,
+                                      const struct iovec *iov,
+                                      size_t niov,
+                                      int **fds,
+                                      size_t *nfds,
+                                      int flags,
+                                      Error **errp)
+{
+    QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc);
+    ssize_t ret;
+
+    if (flags) {
+        error_setg_errno(errp, EINVAL,
+                         _("Flags %x are not supported"),
+                         flags);
+        return -1;
+    }
+    if (fds || nfds) {
+        error_setg_errno(errp, EINVAL, "%s",
+                         _("Channel does not support file descriptor passing"));
+        return -1;
+    }
+
+ retry:
+    ret = readv(fioc->fd, iov, niov);
+    if (ret < 0) {
+        if (errno == EAGAIN) {
+            return -2;
+        }
+        if (errno == EINTR) {
+            goto retry;
+        }
+
+        error_setg_errno(errp, errno, "%s",
+                         _("Unable to read from file"));
+        return -1;
+    }
+
+    return ret;
+}
+
+static ssize_t qio_channel_file_writev(QIOChannel *ioc,
+                                       const struct iovec *iov,
+                                       size_t niov,
+                                       int *fds,
+                                       size_t nfds,
+                                       int flags G_GNUC_UNUSED,
+                                       Error **errp)
+{
+    QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc);
+    ssize_t ret;
+
+    if (flags) {
+        error_setg_errno(errp, EINVAL,
+                         _("Flags %x are not supported"),
+                         flags);
+        return -1;
+    }
+    if (fds || nfds) {
+        error_setg_errno(errp, EINVAL, "%s",
+                         _("Channel does not support file descriptor passing"));
+        return -1;
+    }
+
+ retry:
+    ret = writev(fioc->fd, iov, niov);
+    if (ret <= 0) {
+        if (errno == EAGAIN) {
+            return -2;
+        }
+        if (errno == EINTR) {
+            goto retry;
+        }
+        error_setg_errno(errp, errno, "%s",
+                         _("Unable to write to file"));
+        return -1;
+    }
+    return ret;
+}
+
+static void qio_channel_file_set_blocking(QIOChannel *ioc,
+                                          bool enabled)
+{
+    QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc);
+
+    if (enabled) {
+        qemu_set_block(fioc->fd);
+    } else {
+        qemu_set_nonblock(fioc->fd);
+    }
+}
+
+
+static int qio_channel_file_close(QIOChannel *ioc,
+                                  Error **errp)
+{
+    QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc);
+
+    if (close(fioc->fd) < 0) {
+        error_setg_errno(errp, errno, "%s",
+                         _("Unable to close file"));
+        return -1;
+    }
+    return 0;
+}
+
+
+static GSource *qio_channel_file_create_watch(QIOChannel *ioc,
+                                              GIOCondition condition)
+{
+    QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc);
+    return qio_channel_unix_create_fd_watch(ioc,
+                                            fioc->fd,
+                                            condition);
+}
+
+static void qio_channel_file_class_init(ObjectClass *klass,
+                                        void *class_data G_GNUC_UNUSED)
+{
+    QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass);
+
+    ioc_klass->io_writev = qio_channel_file_writev;
+    ioc_klass->io_readv = qio_channel_file_readv;
+    ioc_klass->io_set_blocking = qio_channel_file_set_blocking;
+    ioc_klass->io_close = qio_channel_file_close;
+    ioc_klass->io_create_watch = qio_channel_file_create_watch;
+}
+
+static const TypeInfo qio_channel_file_info = {
+    .parent = TYPE_QIO_CHANNEL,
+    .name = TYPE_QIO_CHANNEL_FILE,
+    .instance_size = sizeof(QIOChannelFile),
+    .instance_init = qio_channel_file_init,
+    .instance_finalize = qio_channel_file_finalize,
+    .class_init = qio_channel_file_class_init,
+};
+
+static void qio_channel_file_register_types(void)
+{
+    type_register_static(&qio_channel_file_info);
+}
+
+type_init(qio_channel_file_register_types);
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 25/34] io: add QIOTask class for async operations
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (23 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 24/34] io: add QIOChannelFile class Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 15:16   ` Paolo Bonzini
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 26/34] io: add QIOChannelTLS class Daniel P. Berrange
                   ` (9 subsequent siblings)
  34 siblings, 1 reply; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

A number of I/O operations need to be performed asynchronously
to avoid blocking the main loop. The caller of such APIs need
to provide a callback to be invoked on completion/error and
need access to the error, if any. The small QIOTask provides
a simple framework for dealing with such probes. The API
docs inline provide an outline of how this is to be used.

In this series, the QIOTask class will be used for things like
the TLS handshake, the websockets handshake and TCP connect()
progress.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 include/io/task.h | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 io/Makefile.objs  |   1 +
 io/task.c         |  84 +++++++++++++++++++++++++++
 3 files changed, 253 insertions(+)
 create mode 100644 include/io/task.h
 create mode 100644 io/task.c

diff --git a/include/io/task.h b/include/io/task.h
new file mode 100644
index 0000000..8f86902
--- /dev/null
+++ b/include/io/task.h
@@ -0,0 +1,168 @@
+/*
+ * QEMU I/O task
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QIO_TASK_H__
+#define QIO_TASK_H__
+
+#include "qemu-common.h"
+#include "qapi/error.h"
+#include "qom/object.h"
+
+#define TYPE_QIO_TASK "qemu:io-task"
+#define QIO_TASK(obj)                                    \
+    OBJECT_CHECK(QIOTask, (obj), TYPE_QIO_TASK)
+#define QIO_TASK_CLASS(klass)                                   \
+    OBJECT_CLASS_CHECK(QIOTaskClass, klass, TYPE_QIO_TASK)
+#define QIO_TASK_GET_CLASS(obj)                         \
+    OBJECT_GET_CLASS(QIOTaskClass, obj, TYPE_QIO_TASK)
+
+typedef struct QIOTask QIOTask;
+typedef struct QIOTaskClass QIOTaskClass;
+
+typedef void (*QIOTaskFunc)(QIOTask *task,
+                            gpointer opaque);
+
+/**
+ * QIOTask:
+ *
+ * The QIOTask object provides a simple mechanism for reporting
+ * success / failure of long running background operations.
+ *
+ * A object on which the operation is to be performed could have
+ * a public API which accepts a task callback:
+ *
+ *  void myobject_operation(QMyObject *obj,
+ *                           QIOTaskFunc *func,
+ *                           gpointer opaque,
+ *                           GDestroyNotify *notify);
+ *
+ * The 'func' parameter is the callback to be invoked, and 'opaque'
+ * is data to pass to it. The optional 'notify' function is used
+ * to free 'opaque' when no longer needed.
+ *
+ * Now, lets say the implementation of this method wants to set
+ * a timer to run once a second checking for completion of some
+ * activity. It would do something like
+ *
+ *    void myobject_operation(QMyObject *obj,
+ *                            QIOTaskFunc *func,
+ *                            gpointer opaque,
+ *                            GDestroyNotify *notify)
+ *    {
+ *      QIOTask *task;
+ *
+ *      task = qio_task_new(OBJECT(obj), func, opaque, notify);
+ *
+ *      g_timeout_add_full(G_PRIORITY_DEFAULT,
+ *                         1000,
+ *                         myobject_operation_timer,
+ *                         task,
+ *                         (GDestroyNotify)object_unref);
+ *    }
+ *
+ * It could equally have setup a watch on a file descriptor or
+ * created a background thread, or something else entirely.
+ * Notice that the source object is passed to the task, and
+ * QIOTask will hold a reference on that. This ensure that
+ * the QMyObject instance cannot be garbage collected while
+ * the async task is still in progress.
+ *
+ * In this case, myobject_operation_timer will fire after
+ * 3 secs and do
+ *
+ *   gboolean myobject_operation_timer(gpointer opaque)
+ *   {
+ *      QIOTask *task = QIO_TASK(opaque);
+ *      Error *err;*
+ *
+ *      ...check something important...
+ *       if (err) {
+ *           qio_task_abort(task, err);
+ *           error_free(task);
+ *           return FALSE;
+ *       } else if (...work is completed ...) {
+ *           qio_task_complete(task);
+ *           return FALSE;
+ *       }
+ *       ...carry on polling ...
+ *       return TRUE;
+ *   }
+ *
+ * Once this function returns false, object_unref will be called
+ * automatically on the task causing it to be released and the
+ * ref on QMyObject dropped too.
+ */
+
+struct QIOTask {
+    Object parent;
+    Object *source;
+    QIOTaskFunc func;
+    gpointer opaque;
+    GDestroyNotify destroy;
+    Error *err;
+};
+
+struct QIOTaskClass {
+    ObjectClass parent;
+};
+
+/**
+ * qio_task_new:
+ * @source: the object on which the operation is invoked
+ * @func: the callback to invoke when the task completes
+ * @opaque: opaque data to pass to @func when invoked
+ * @destroy: optional callback to free @opaque
+ *
+ * Creates a new task object to track completion of a
+ * background operation running on the object @source.
+ * When the operation completes or fails, the callback
+ * @func will be invoked. The callback can access the
+ * 'err' attribute in the task object to determine if
+ * the operation was successful or not.
+ *
+ * Returns: the task object
+ */
+QIOTask *qio_task_new(Object *source,
+                      QIOTaskFunc func,
+                      gpointer opaque,
+                      GDestroyNotify destroy);
+
+/**
+ * qio_task_complete:
+ * @task: the task object
+ *
+ * Mark the operation as succesfully completed
+ */
+void qio_task_complete(QIOTask *task);
+
+/**
+ * qio_task_abort:
+ * @task: the task object
+ * @err: the error to record for the operation
+ *
+ * Mark the operation as failed, with @err providing
+ * details about the failure. The @err may be freed
+ * afer the function returns, as the notification
+ * callback is invoked synchronously.
+ */
+void qio_task_abort(QIOTask *task,
+                    Error *err);
+
+#endif /* QIO_TASK_H__ */
diff --git a/io/Makefile.objs b/io/Makefile.objs
index eec1b43..b9973ac 100644
--- a/io/Makefile.objs
+++ b/io/Makefile.objs
@@ -1,3 +1,4 @@
+util-obj-y += task.o
 util-obj-y += channel.o
 util-obj-y += channel-unix.o
 util-obj-y += channel-socket.o
diff --git a/io/task.c b/io/task.c
new file mode 100644
index 0000000..453f474
--- /dev/null
+++ b/io/task.c
@@ -0,0 +1,84 @@
+/*
+ * QEMU I/O task
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "io/task.h"
+
+QIOTask *qio_task_new(Object *source,
+                      QIOTaskFunc func,
+                      gpointer opaque,
+                      GDestroyNotify destroy)
+{
+    QIOTask *task;
+
+    task = QIO_TASK(object_new(TYPE_QIO_TASK));
+
+    task->source = source;
+    object_ref(source);
+    task->func = func;
+    task->opaque = opaque;
+    task->destroy = destroy;
+
+    return task;
+}
+
+void qio_task_complete(QIOTask *task)
+{
+    task->func(task, task->opaque);
+}
+
+void qio_task_abort(QIOTask *task,
+                    Error *err)
+{
+    task->err = err;
+    task->func(task, task->opaque);
+    task->err = NULL;
+}
+
+static void qio_task_initfn(Object *obj G_GNUC_UNUSED)
+{
+    /* nada */
+}
+
+static void qio_task_finalize(Object *obj)
+{
+    QIOTask *task = QIO_TASK(obj);
+
+    object_unref(task->source);
+    if (task->destroy) {
+        task->destroy(task->opaque);
+    }
+}
+
+
+static const TypeInfo qio_task_info = {
+    .parent = TYPE_OBJECT,
+    .name = TYPE_QIO_TASK,
+    .instance_size = sizeof(QIOTask),
+    .instance_init = qio_task_initfn,
+    .instance_finalize = qio_task_finalize,
+    .class_size = sizeof(QIOTaskClass),
+};
+
+static void qio_task_register_types(void)
+{
+    type_register_static(&qio_task_info);
+}
+
+type_init(qio_task_register_types);
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 26/34] io: add QIOChannelTLS class
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (24 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 25/34] io: add QIOTask class for async operations Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 27/34] io: pull Buffer code out of VNC module Daniel P. Berrange
                   ` (8 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

Add a QIOChannel subclass that can run the TLS protocol over the
top of another QIOChannel instance.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 include/io/channel-tls.h | 142 +++++++++++++++++
 io/Makefile.objs         |   1 +
 io/channel-tls.c         | 393 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 536 insertions(+)
 create mode 100644 include/io/channel-tls.h
 create mode 100644 io/channel-tls.c

diff --git a/include/io/channel-tls.h b/include/io/channel-tls.h
new file mode 100644
index 0000000..7023b0a
--- /dev/null
+++ b/include/io/channel-tls.h
@@ -0,0 +1,142 @@
+/*
+ * QEMU I/O channels TLS driver
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QIO_CHANNEL_TLS_H__
+#define QIO_CHANNEL_TLS_H__
+
+#include "io/channel.h"
+#include "io/task.h"
+#include "crypto/tlssession.h"
+
+#define TYPE_QIO_CHANNEL_TLS "qemu:io-channel-tls"
+#define QIO_CHANNEL_TLS(obj)                                     \
+    OBJECT_CHECK(QIOChannelTLS, (obj), TYPE_QIO_CHANNEL_TLS)
+
+typedef struct QIOChannelTLS QIOChannelTLS;
+
+/**
+ * QIOChannelTLS
+ *
+ * The QIOChannelTLS class provides a channel wrapper which
+ * can transparently run the TLS encryption protocol. It is
+ * usually used over a TCP socket, but there is actually no
+ * technical restriction on which type of master channel is
+ * used as the transport.
+ *
+ * This channel object is capable of running as either a
+ * TLS server or TLS client.
+ */
+
+struct QIOChannelTLS {
+    QIOChannel parent;
+    QIOChannel *master;
+    QCryptoTLSSession *session;
+};
+
+/**
+ * qio_channel_tls_new_server:
+ * @master: the underlying channel object
+ * @creds: the credentials to use for TLS handshake
+ * @aclname: the access control list for validating clients
+ * @errp: pointer to an uninitialized error object
+ *
+ * Create a new TLS channel that runs the server side of
+ * a TLS session. The TLS session handshake will use the
+ * credentials provided in @creds. If the @aclname parameter
+ * is non-NULL, then the client will have to provide
+ * credentials (ie a x509 client certificate) which will
+ * then be validated against the ACL.
+ *
+ * After creating the channel, it is mandatory to call
+ * the qio_channel_tls_handshake() method before attempting
+ * todo any I/O on the channel.
+ *
+ * Once the handshake has completed, all I/O should be done
+ * via the new TLS channel object and not the original
+ * master channel
+ *
+ * Returns: the new TLS channel object, or NULL
+ */
+QIOChannelTLS *
+qio_channel_tls_new_server(QIOChannel *master,
+                           QCryptoTLSCreds *creds,
+                           const char *aclname,
+                           Error **errp);
+
+/**
+ * qio_channel_tls_new_client:
+ * @master: the underlying channel object
+ * @creds: the credentials to use for TLS handshake
+ * @hostname: the user specified server hostname
+ * @errp: pointer to an uninitialized error object
+ *
+ * Create a new TLS channel that runs the client side of
+ * a TLS session. The TLS session handshake will use the
+ * credentials provided in @creds. The @hostname parameter
+ * should provide the user specified hostname of the server
+ * and will be validated against the server's credentials
+ * (ie CommonName of the x509 certificate)
+ *
+ * After creating the channel, it is mandatory to call
+ * the qio_channel_tls_handshake() method before attempting
+ * todo any I/O on the channel.
+ *
+ * Once the handshake has completed, all I/O should be done
+ * via the new TLS channel object and not the original
+ * master channel
+ *
+ * Returns: the new TLS channel object, or NULL
+ */
+QIOChannelTLS *
+qio_channel_tls_new_client(QIOChannel *master,
+                           QCryptoTLSCreds *creds,
+                           const char *hostname,
+                           Error **errp);
+
+/**
+ * qio_channel_tls_handshake:
+ * @ioc: the TLS channel object
+ * @func: the callback to invoke when completed
+ * @opaque: opaque data to pass to @func
+ * @destroy: optional callback to free @opaque
+ *
+ * Perform the TLS session handshake. This method
+ * will return immediately and the handshake will
+ * continue in the background, provided the main
+ * loop is running. When the handshake is complete,
+ * or fails, the @func callback will be invoked.
+ */
+void qio_channel_tls_handshake(QIOChannelTLS *ioc,
+                               QIOTaskFunc func,
+                               gpointer opaque,
+                               GDestroyNotify destroy);
+
+/**
+ * qio_channel_tls_get_session:
+ * @ioc: the TLS channel object
+ *
+ * Get the TLS session used by the channel.
+ *
+ * Returns: the TLS session
+ */
+QCryptoTLSSession *
+qio_channel_tls_get_session(QIOChannelTLS *ioc);
+
+#endif /* QIO_CHANNEL_TLS_H__ */
diff --git a/io/Makefile.objs b/io/Makefile.objs
index b9973ac..3bf7656 100644
--- a/io/Makefile.objs
+++ b/io/Makefile.objs
@@ -3,3 +3,4 @@ util-obj-y += channel.o
 util-obj-y += channel-unix.o
 util-obj-y += channel-socket.o
 util-obj-y += channel-file.o
+util-obj-y += channel-tls.o
diff --git a/io/channel-tls.c b/io/channel-tls.c
new file mode 100644
index 0000000..4f5f2e9
--- /dev/null
+++ b/io/channel-tls.c
@@ -0,0 +1,393 @@
+/*
+ * QEMU I/O channels TLS driver
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <glib/gi18n.h>
+
+#include "io/channel-tls.h"
+
+#define QIO_DEBUG
+
+#ifdef QIO_DEBUG
+#define DPRINTF(fmt, ...) do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do { } while (0)
+#endif
+
+
+static ssize_t qio_channel_tls_write_handler(const char *buf,
+                                             size_t len,
+                                             void *opaque)
+{
+    QIOChannelTLS *tioc = QIO_CHANNEL_TLS(opaque);
+    int ret;
+
+    ret = qio_channel_write(tioc->master, buf, len, NULL);
+    if (ret == QIO_CHANNEL_ERR_BLOCK) {
+        errno = EAGAIN;
+        return -1;
+    } else if (ret < 0) {
+        errno = EIO;
+        return -1;
+    }
+    return ret;
+}
+
+static ssize_t qio_channel_tls_read_handler(char *buf,
+                                            size_t len,
+                                            void *opaque)
+{
+    QIOChannelTLS *tioc = QIO_CHANNEL_TLS(opaque);
+    int ret;
+
+    ret = qio_channel_read(tioc->master, buf, len, NULL);
+    if (ret == QIO_CHANNEL_ERR_BLOCK) {
+        errno = EAGAIN;
+        return -1;
+    } else if (ret < 0) {
+        errno = EIO;
+        return -1;
+    }
+    return ret;
+}
+
+
+QIOChannelTLS *
+qio_channel_tls_new_server(QIOChannel *master,
+                           QCryptoTLSCreds *creds,
+                           const char *aclname,
+                           Error **errp)
+{
+    QIOChannelTLS *ioc;
+
+    ioc = QIO_CHANNEL_TLS(object_new(TYPE_QIO_CHANNEL_TLS));
+
+    ioc->master = master;
+    object_ref(OBJECT(master));
+
+    ioc->session = qcrypto_tls_session_new(
+        creds,
+        NULL,
+        aclname,
+        QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
+        errp);
+    if (!ioc->session) {
+        goto error;
+    }
+
+    qcrypto_tls_session_set_callbacks(
+        ioc->session,
+        qio_channel_tls_write_handler,
+        qio_channel_tls_read_handler,
+        ioc);
+
+    return ioc;
+
+ error:
+    DPRINTF("Session setup failed %s\n",
+            error_get_pretty(*errp));
+    object_unref(OBJECT(ioc));
+    return NULL;
+}
+
+QIOChannelTLS *
+qio_channel_tls_new_client(QIOChannel *master,
+                           QCryptoTLSCreds *creds,
+                           const char *hostname,
+                           Error **errp)
+{
+    QIOChannelTLS *ioc;
+
+    ioc = QIO_CHANNEL_TLS(object_new(TYPE_QIO_CHANNEL_TLS));
+
+    ioc->master = master;
+    object_ref(OBJECT(master));
+
+    ioc->session = qcrypto_tls_session_new(
+        creds,
+        hostname,
+        NULL,
+        QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT,
+        errp);
+    if (!ioc->session) {
+        goto error;
+    }
+
+    qcrypto_tls_session_set_callbacks(
+        ioc->session,
+        qio_channel_tls_write_handler,
+        qio_channel_tls_read_handler,
+        ioc);
+
+    return ioc;
+
+ error:
+    DPRINTF("Session setup failed %s\n",
+            error_get_pretty(*errp));
+    object_unref(OBJECT(ioc));
+    return NULL;
+}
+
+
+static gboolean qio_channel_tls_handshake_io(QIOChannel *ioc,
+                                             GIOCondition condition,
+                                             gpointer user_data);
+
+static void qio_channel_tls_handshake_task(QIOChannelTLS *ioc,
+                                           QIOTask *task)
+{
+    Error *err = NULL;
+    QCryptoTLSSessionHandshakeStatus status;
+
+    if (qcrypto_tls_session_handshake(ioc->session, &err) < 0) {
+        qio_task_abort(task, err);
+        goto cleanup;
+    }
+
+    status = qcrypto_tls_session_get_handshake_status(ioc->session);
+    if (status == QCRYPTO_TLS_HANDSHAKE_COMPLETE) {
+        if (qcrypto_tls_session_check_credentials(ioc->session,
+                                                  &err) < 0) {
+            DPRINTF("Check creds failed session=%p err=%s\n",
+                    ioc->session, error_get_pretty(err));
+            qio_task_abort(task, err);
+            goto cleanup;
+        }
+        DPRINTF("Handshake compelte session=%p\n",
+                ioc->session);
+        qio_task_complete(task);
+    } else {
+        GIOCondition condition;
+        DPRINTF("Handshake still running %d\n", status);
+        if (status == QCRYPTO_TLS_HANDSHAKE_SENDING) {
+            condition = G_IO_OUT;
+        } else {
+            condition = G_IO_IN;
+        }
+
+        object_ref(OBJECT(task));
+        qio_channel_add_watch_full(ioc->master,
+                                   G_PRIORITY_DEFAULT,
+                                   condition,
+                                   qio_channel_tls_handshake_io,
+                                   task,
+                                   (GDestroyNotify)object_unref);
+    }
+
+ cleanup:
+    error_free(err);
+}
+
+
+static gboolean qio_channel_tls_handshake_io(QIOChannel *ioc,
+                                             GIOCondition condition,
+                                             gpointer user_data)
+{
+    QIOTask *task = QIO_TASK(user_data);
+
+    qio_channel_tls_handshake_task(
+        QIO_CHANNEL_TLS(task->source), task);
+
+    return FALSE;
+}
+
+void qio_channel_tls_handshake(QIOChannelTLS *ioc,
+                               QIOTaskFunc func,
+                               gpointer opaque,
+                               GDestroyNotify destroy)
+{
+    QIOTask *task;
+
+    task = qio_task_new(OBJECT(ioc),
+                        func, opaque, destroy);
+
+    qio_channel_tls_handshake_task(ioc, task);
+
+    object_unref(OBJECT(task));
+}
+
+
+static void qio_channel_tls_init(Object *obj G_GNUC_UNUSED)
+{
+}
+
+
+static void qio_channel_tls_finalize(Object *obj)
+{
+    QIOChannelTLS *ioc = QIO_CHANNEL_TLS(obj);
+
+    object_unref(OBJECT(ioc->master));
+    qcrypto_tls_session_free(ioc->session);
+}
+
+
+static ssize_t qio_channel_tls_readv(QIOChannel *ioc,
+                                     const struct iovec *iov,
+                                     size_t niov,
+                                     int **fds,
+                                     size_t *nfds,
+                                     int flags,
+                                     Error **errp)
+{
+    QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc);
+    size_t i;
+    ssize_t got = 0;
+
+    if (fds || nfds) {
+        error_setg(errp, "%s",
+                   _("Cannot receive file descriptors over TLS channel"));
+        return -1;
+    }
+    if (flags) {
+        error_setg(errp, "Flags %x not supported in TLS channel", flags);
+        return  -1;
+    }
+
+    for (i = 0 ; i < niov ; i++) {
+        ssize_t ret = qcrypto_tls_session_read(tioc->session,
+                                               iov->iov_base,
+                                               iov->iov_len);
+
+        if (ret < 0) {
+            if (errno == EAGAIN) {
+                if (got) {
+                    return got;
+                } else {
+                    return QIO_CHANNEL_ERR_BLOCK;
+                }
+            }
+
+            error_setg_errno(errp, errno, "%s",
+                             _("Cannot read from TLS channel"));
+            return -1;
+        }
+        got += ret;
+        if (ret < iov->iov_len) {
+            break;
+        }
+    }
+    return got;
+}
+
+
+static ssize_t qio_channel_tls_writev(QIOChannel *ioc,
+                                      const struct iovec *iov,
+                                      size_t niov,
+                                      int *fds,
+                                      size_t nfds,
+                                      int flags,
+                                      Error **errp)
+{
+    QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc);
+    size_t i;
+    ssize_t done = 0;
+
+    if (fds || nfds) {
+        error_setg(errp, "%s",
+                   _("Cannot send file descriptors over TLS channel"));
+        return -1;
+    }
+    if (flags) {
+        error_setg(errp, "Flags %x not supported in TLS channel", flags);
+        return  -1;
+    }
+
+    for (i = 0 ; i < niov ; i++) {
+        ssize_t ret = qcrypto_tls_session_write(tioc->session,
+                                                iov->iov_base,
+                                                iov->iov_len);
+
+        if (ret <= 0) {
+            if (errno == EAGAIN) {
+                if (done) {
+                    return done;
+                } else {
+                    return QIO_CHANNEL_ERR_BLOCK;
+                }
+            }
+
+            error_setg_errno(errp, errno, "%s",
+                             _("Cannot write to TLS channel"));
+            return -1;
+        }
+        done += ret;
+        if (ret < iov->iov_len) {
+            break;
+        }
+    }
+    return done;
+}
+
+static void qio_channel_tls_set_blocking(QIOChannel *ioc,
+                                         bool enabled)
+{
+    QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc);
+
+    qio_channel_set_blocking(tioc->master, enabled);
+}
+
+static int qio_channel_tls_close(QIOChannel *ioc,
+                                 Error **errp)
+{
+    QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc);
+
+    return qio_channel_close(tioc->master, errp);
+}
+
+static GSource *qio_channel_tls_create_watch(QIOChannel *ioc,
+                                             GIOCondition condition)
+{
+    QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc);
+
+    return qio_channel_create_watch(tioc->master, condition);
+}
+
+QCryptoTLSSession *
+qio_channel_tls_get_session(QIOChannelTLS *ioc)
+{
+    return ioc->session;
+}
+
+static void qio_channel_tls_class_init(ObjectClass *klass,
+                                       void *class_data G_GNUC_UNUSED)
+{
+    QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass);
+
+    ioc_klass->io_writev = qio_channel_tls_writev;
+    ioc_klass->io_readv = qio_channel_tls_readv;
+    ioc_klass->io_set_blocking = qio_channel_tls_set_blocking;
+    ioc_klass->io_close = qio_channel_tls_close;
+    ioc_klass->io_create_watch = qio_channel_tls_create_watch;
+}
+
+static const TypeInfo qio_channel_tls_info = {
+    .parent = TYPE_QIO_CHANNEL,
+    .name = TYPE_QIO_CHANNEL_TLS,
+    .instance_size = sizeof(QIOChannelTLS),
+    .instance_init = qio_channel_tls_init,
+    .instance_finalize = qio_channel_tls_finalize,
+    .class_init = qio_channel_tls_class_init,
+};
+
+static void qio_channel_tls_register_types(void)
+{
+    type_register_static(&qio_channel_tls_info);
+}
+
+type_init(qio_channel_tls_register_types);
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 27/34] io: pull Buffer code out of VNC module
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (25 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 26/34] io: add QIOChannelTLS class Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 28/34] io: add QIOChannelWebsock class Daniel P. Berrange
                   ` (7 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

The Buffer code in the VNC server is useful for the IO channel
code, so pull it out into a shared module, QIOBuffer.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 include/io/buffer.h | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 io/Makefile.objs    |   1 +
 io/buffer.c         |  65 +++++++++++++++++++++++++++++
 ui/vnc-auth-sasl.c  |   4 +-
 ui/vnc-enc-tight.c  |  38 ++++++++---------
 ui/vnc-enc-zlib.c   |   6 +--
 ui/vnc-enc-zrle.c   |  18 ++++----
 ui/vnc-jobs.c       |  15 +++----
 ui/vnc-ws.c         |  36 ++++++++--------
 ui/vnc-ws.h         |   6 +--
 ui/vnc.c            |  67 ++++++-----------------------
 ui/vnc.h            |  50 ++++++++--------------
 12 files changed, 276 insertions(+), 148 deletions(-)
 create mode 100644 include/io/buffer.h
 create mode 100644 io/buffer.c

diff --git a/include/io/buffer.h b/include/io/buffer.h
new file mode 100644
index 0000000..2b1b261
--- /dev/null
+++ b/include/io/buffer.h
@@ -0,0 +1,118 @@
+/*
+ * QEMU I/O buffers
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QIO_BUFFER_H__
+#define QIO_BUFFER_H__
+
+#include "qemu-common.h"
+
+typedef struct QIOBuffer QIOBuffer;
+
+/**
+ * QIOBuffer:
+ *
+ * The QIOBuffer object provides a simple dynamically resizing
+ * array, with separate tracking of capacity and usage. This
+ * is typically useful when buffering I/O data.
+ */
+
+struct QIOBuffer {
+    size_t capacity;
+    size_t offset;
+    uint8_t *buffer;
+};
+
+/**
+ * qio_buffer_reserve:
+ * @buffer: the buffer object
+ * @len: the minimum required free space
+ *
+ * Ensure that the buffer has space allocated for at least
+ * @len bytes. If the current buffer is too small, it will
+ * be reallocated, possibly to a larger size than requested.
+ */
+void qio_buffer_reserve(QIOBuffer *buffer, size_t len);
+
+/**
+ * qio_buffer_reset:
+ * @buffer: the buffer object
+ *
+ * Reset the length of the stored data to zero, but do
+ * not free / reallocate the memory buffer
+ */
+void qio_buffer_reset(QIOBuffer *buffer);
+
+/**
+ * qio_buffer_free:
+ * @buffer: the buffer object
+ *
+ * Reset the length of the stored data to zero and also
+ * free the internal memory buffer
+ */
+void qio_buffer_free(QIOBuffer *buffer);
+
+/**
+ * qio_buffer_append:
+ * @buffer: the buffer object
+ * @data: the data block to append
+ * @len: the length of @data in bytes
+ *
+ * Append the contents of @data to the end of the buffer.
+ * The caller must ensure that the buffer has sufficient
+ * free space for @len bytes, typically by calling the
+ * qio_buffer_reserve() method prior to appending.
+ */
+void qio_buffer_append(QIOBuffer *buffer, const void *data, size_t len);
+
+/**
+ * qio_buffer_advance:
+ * @buffer: the buffer object
+ * @len: the number of bytes to skip
+ *
+ * Remove @len bytes of data from the head of the buffer.
+ * The internal buffer will not be reallocated, so will
+ * have at least @len bytes of free space after this
+ * call completes
+ */
+void qio_buffer_advance(QIOBuffer *buffer, size_t len);
+
+/**
+ * qio_buffer_end:
+ * @buffer: the buffer object
+ *
+ * Get a pointer to the tail end of the internal buffer
+ * The returned pointer is only valid until the next
+ * call to qio_buffer_reserve().
+ *
+ * Returns: the tail of the buffer
+ */
+uint8_t *qio_buffer_end(QIOBuffer *buffer);
+
+/**
+ * qio_buffer_empty:
+ * @buffer: the buffer object
+ *
+ * Determine if the buffer contains any current data
+ *
+ * Returns: true if the buffer holds data, false otherwise
+ */
+gboolean qio_buffer_empty(QIOBuffer *buffer);
+
+#endif /* QIO_BUFFER_H__ */
diff --git a/io/Makefile.objs b/io/Makefile.objs
index 3bf7656..b415753 100644
--- a/io/Makefile.objs
+++ b/io/Makefile.objs
@@ -1,3 +1,4 @@
+util-obj-y += buffer.o
 util-obj-y += task.o
 util-obj-y += channel.o
 util-obj-y += channel-unix.o
diff --git a/io/buffer.c b/io/buffer.c
new file mode 100644
index 0000000..68ae68d
--- /dev/null
+++ b/io/buffer.c
@@ -0,0 +1,65 @@
+/*
+ * QEMU I/O buffers
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "io/buffer.h"
+
+void qio_buffer_reserve(QIOBuffer *buffer, size_t len)
+{
+    if ((buffer->capacity - buffer->offset) < len) {
+        buffer->capacity += (len + 1024);
+        buffer->buffer = g_realloc(buffer->buffer, buffer->capacity);
+    }
+}
+
+gboolean qio_buffer_empty(QIOBuffer *buffer)
+{
+    return buffer->offset == 0;
+}
+
+uint8_t *qio_buffer_end(QIOBuffer *buffer)
+{
+    return buffer->buffer + buffer->offset;
+}
+
+void qio_buffer_reset(QIOBuffer *buffer)
+{
+    buffer->offset = 0;
+}
+
+void qio_buffer_free(QIOBuffer *buffer)
+{
+    g_free(buffer->buffer);
+    buffer->offset = 0;
+    buffer->capacity = 0;
+    buffer->buffer = NULL;
+}
+
+void qio_buffer_append(QIOBuffer *buffer, const void *data, size_t len)
+{
+    memcpy(buffer->buffer + buffer->offset, data, len);
+    buffer->offset += len;
+}
+
+void qio_buffer_advance(QIOBuffer *buffer, size_t len)
+{
+    memmove(buffer->buffer, buffer->buffer + len,
+            (buffer->offset - len));
+    buffer->offset -= len;
+}
diff --git a/ui/vnc-auth-sasl.c b/ui/vnc-auth-sasl.c
index 2ddd259..9733dfc 100644
--- a/ui/vnc-auth-sasl.c
+++ b/ui/vnc-auth-sasl.c
@@ -113,8 +113,8 @@ long vnc_client_read_sasl(VncState *vs)
         return vnc_client_io_error(vs, -1, -EIO);
     VNC_DEBUG("Read SASL Encoded %p size %ld Decoded %p size %d\n",
               encoded, ret, decoded, decodedLen);
-    buffer_reserve(&vs->input, decodedLen);
-    buffer_append(&vs->input, decoded, decodedLen);
+    qio_buffer_reserve(&vs->input, decodedLen);
+    qio_buffer_append(&vs->input, decoded, decodedLen);
     return decodedLen;
 }
 
diff --git a/ui/vnc-enc-tight.c b/ui/vnc-enc-tight.c
index 9a9ddf2..772ec79 100644
--- a/ui/vnc-enc-tight.c
+++ b/ui/vnc-enc-tight.c
@@ -856,7 +856,7 @@ static int tight_compress_data(VncState *vs, int stream_id, size_t bytes,
     }
 
     /* reserve memory in output buffer */
-    buffer_reserve(&vs->tight.zlib, bytes + 64);
+    qio_buffer_reserve(&vs->tight.zlib, bytes + 64);
 
     /* set pointers */
     zstream->next_in = vs->tight.tight.buffer;
@@ -879,7 +879,7 @@ static int tight_compress_data(VncState *vs, int stream_id, size_t bytes,
     tight_send_compact_size(vs, bytes);
     vnc_write(vs, vs->tight.zlib.buffer, bytes);
 
-    buffer_reset(&vs->tight.zlib);
+    qio_buffer_reset(&vs->tight.zlib);
 
     return bytes;
 }
@@ -1053,7 +1053,7 @@ static bool send_gradient_rect(VncState *vs, int x, int y, int w, int h)
     vnc_write_u8(vs, (stream | VNC_TIGHT_EXPLICIT_FILTER) << 4);
     vnc_write_u8(vs, VNC_TIGHT_FILTER_GRADIENT);
 
-    buffer_reserve(&vs->tight.gradient, w * 3 * sizeof (int));
+    qio_buffer_reserve(&vs->tight.gradient, w * 3 * sizeof(int));
 
     if (vs->tight.pixel24) {
         tight_filter_gradient24(vs, vs->tight.tight.buffer, w, h);
@@ -1066,7 +1066,7 @@ static bool send_gradient_rect(VncState *vs, int x, int y, int w, int h)
         bytes = 2;
     }
 
-    buffer_reset(&vs->tight.gradient);
+    qio_buffer_reset(&vs->tight.gradient);
 
     bytes = w * h * bytes;
     vs->tight.tight.offset = bytes;
@@ -1149,7 +1149,7 @@ static int send_palette_rect(VncState *vs, int x, int y,
 static void jpeg_init_destination(j_compress_ptr cinfo)
 {
     VncState *vs = cinfo->client_data;
-    Buffer *buffer = &vs->tight.jpeg;
+    QIOBuffer *buffer = &vs->tight.jpeg;
 
     cinfo->dest->next_output_byte = (JOCTET *)buffer->buffer + buffer->offset;
     cinfo->dest->free_in_buffer = (size_t)(buffer->capacity - buffer->offset);
@@ -1159,10 +1159,10 @@ static void jpeg_init_destination(j_compress_ptr cinfo)
 static boolean jpeg_empty_output_buffer(j_compress_ptr cinfo)
 {
     VncState *vs = cinfo->client_data;
-    Buffer *buffer = &vs->tight.jpeg;
+    QIOBuffer *buffer = &vs->tight.jpeg;
 
     buffer->offset = buffer->capacity;
-    buffer_reserve(buffer, 2048);
+    qio_buffer_reserve(buffer, 2048);
     jpeg_init_destination(cinfo);
     return TRUE;
 }
@@ -1171,7 +1171,7 @@ static boolean jpeg_empty_output_buffer(j_compress_ptr cinfo)
 static void jpeg_term_destination(j_compress_ptr cinfo)
 {
     VncState *vs = cinfo->client_data;
-    Buffer *buffer = &vs->tight.jpeg;
+    QIOBuffer *buffer = &vs->tight.jpeg;
 
     buffer->offset = buffer->capacity - cinfo->dest->free_in_buffer;
 }
@@ -1190,7 +1190,7 @@ static int send_jpeg_rect(VncState *vs, int x, int y, int w, int h, int quality)
         return send_full_color_rect(vs, x, y, w, h);
     }
 
-    buffer_reserve(&vs->tight.jpeg, 2048);
+    qio_buffer_reserve(&vs->tight.jpeg, 2048);
 
     cinfo.err = jpeg_std_error(&jerr);
     jpeg_create_compress(&cinfo);
@@ -1227,7 +1227,7 @@ static int send_jpeg_rect(VncState *vs, int x, int y, int w, int h, int quality)
 
     tight_send_compact_size(vs, vs->tight.jpeg.offset);
     vnc_write(vs, vs->tight.jpeg.buffer, vs->tight.jpeg.offset);
-    buffer_reset(&vs->tight.jpeg);
+    qio_buffer_reset(&vs->tight.jpeg);
 
     return 1;
 }
@@ -1270,7 +1270,7 @@ static void png_write_data(png_structp png_ptr, png_bytep data,
 {
     VncState *vs = png_get_io_ptr(png_ptr);
 
-    buffer_reserve(&vs->tight.png, vs->tight.png.offset + length);
+    qio_buffer_reserve(&vs->tight.png, vs->tight.png.offset + length);
     memcpy(vs->tight.png.buffer + vs->tight.png.offset, data, length);
 
     vs->tight.png.offset += length;
@@ -1351,7 +1351,7 @@ static int send_png_rect(VncState *vs, int x, int y, int w, int h,
 
     png_write_info(png_ptr, info_ptr);
 
-    buffer_reserve(&vs->tight.png, 2048);
+    qio_buffer_reserve(&vs->tight.png, 2048);
     linebuf = qemu_pixman_linebuf_create(PIXMAN_BE_r8g8b8, w);
     buf = (uint8_t *)pixman_image_get_data(linebuf);
     for (dy = 0; dy < h; dy++)
@@ -1377,14 +1377,14 @@ static int send_png_rect(VncState *vs, int x, int y, int w, int h,
 
     tight_send_compact_size(vs, vs->tight.png.offset);
     vnc_write(vs, vs->tight.png.buffer, vs->tight.png.offset);
-    buffer_reset(&vs->tight.png);
+    qio_buffer_reset(&vs->tight.png);
     return 1;
 }
 #endif /* CONFIG_VNC_PNG */
 
 static void vnc_tight_start(VncState *vs)
 {
-    buffer_reset(&vs->tight.tight);
+    qio_buffer_reset(&vs->tight.tight);
 
     // make the output buffer be the zlib buffer, so we can compress it later
     vs->tight.tmp = vs->output;
@@ -1686,13 +1686,13 @@ void vnc_tight_clear(VncState *vs)
         }
     }
 
-    buffer_free(&vs->tight.tight);
-    buffer_free(&vs->tight.zlib);
-    buffer_free(&vs->tight.gradient);
+    qio_buffer_free(&vs->tight.tight);
+    qio_buffer_free(&vs->tight.zlib);
+    qio_buffer_free(&vs->tight.gradient);
 #ifdef CONFIG_VNC_JPEG
-    buffer_free(&vs->tight.jpeg);
+    qio_buffer_free(&vs->tight.jpeg);
 #endif
 #ifdef CONFIG_VNC_PNG
-    buffer_free(&vs->tight.png);
+    qio_buffer_free(&vs->tight.png);
 #endif
 }
diff --git a/ui/vnc-enc-zlib.c b/ui/vnc-enc-zlib.c
index d1b97f2..47ba146 100644
--- a/ui/vnc-enc-zlib.c
+++ b/ui/vnc-enc-zlib.c
@@ -47,7 +47,7 @@ void vnc_zlib_zfree(void *x, void *addr)
 
 static void vnc_zlib_start(VncState *vs)
 {
-    buffer_reset(&vs->zlib.zlib);
+    qio_buffer_reset(&vs->zlib.zlib);
 
     // make the output buffer be the zlib buffer, so we can compress it later
     vs->zlib.tmp = vs->output;
@@ -96,7 +96,7 @@ static int vnc_zlib_stop(VncState *vs)
     }
 
     // reserve memory in output buffer
-    buffer_reserve(&vs->output, vs->zlib.zlib.offset + 64);
+    qio_buffer_reserve(&vs->output, vs->zlib.zlib.offset + 64);
 
     // set pointers
     zstream->next_in = vs->zlib.zlib.buffer;
@@ -148,5 +148,5 @@ void vnc_zlib_clear(VncState *vs)
     if (vs->zlib.stream.opaque) {
         deflateEnd(&vs->zlib.stream);
     }
-    buffer_free(&vs->zlib.zlib);
+    qio_buffer_free(&vs->zlib.zlib);
 }
diff --git a/ui/vnc-enc-zrle.c b/ui/vnc-enc-zrle.c
index ed3b484..bd1e320 100644
--- a/ui/vnc-enc-zrle.c
+++ b/ui/vnc-enc-zrle.c
@@ -36,7 +36,7 @@ static const int bits_per_packed_pixel[] = {
 
 static void vnc_zrle_start(VncState *vs)
 {
-    buffer_reset(&vs->zrle.zrle);
+    qio_buffer_reset(&vs->zrle.zrle);
 
     /* make the output buffer be the zlib buffer, so we can compress it later */
     vs->zrle.tmp = vs->output;
@@ -53,10 +53,10 @@ static void vnc_zrle_stop(VncState *vs)
 static void *zrle_convert_fb(VncState *vs, int x, int y, int w, int h,
                              int bpp)
 {
-    Buffer tmp;
+    QIOBuffer tmp;
 
-    buffer_reset(&vs->zrle.fb);
-    buffer_reserve(&vs->zrle.fb, w * h * bpp + bpp);
+    qio_buffer_reset(&vs->zrle.fb);
+    qio_buffer_reserve(&vs->zrle.fb, w * h * bpp + bpp);
 
     tmp = vs->output;
     vs->output = vs->zrle.fb;
@@ -72,7 +72,7 @@ static int zrle_compress_data(VncState *vs, int level)
 {
     z_streamp zstream = &vs->zrle.stream;
 
-    buffer_reset(&vs->zrle.zlib);
+    qio_buffer_reset(&vs->zrle.zlib);
 
     if (zstream->opaque != vs) {
         int err;
@@ -92,7 +92,7 @@ static int zrle_compress_data(VncState *vs, int level)
     }
 
     /* reserve memory in output buffer */
-    buffer_reserve(&vs->zrle.zlib, vs->zrle.zrle.offset + 64);
+    qio_buffer_reserve(&vs->zrle.zlib, vs->zrle.zrle.offset + 64);
 
     /* set pointers */
     zstream->next_in = vs->zrle.zrle.buffer;
@@ -360,7 +360,7 @@ void vnc_zrle_clear(VncState *vs)
     if (vs->zrle.stream.opaque) {
         deflateEnd(&vs->zrle.stream);
     }
-    buffer_free(&vs->zrle.zrle);
-    buffer_free(&vs->zrle.fb);
-    buffer_free(&vs->zrle.zlib);
+    qio_buffer_free(&vs->zrle.zrle);
+    qio_buffer_free(&vs->zrle.fb);
+    qio_buffer_free(&vs->zrle.zlib);
 }
diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c
index c8ee203..333c35e 100644
--- a/ui/vnc-jobs.c
+++ b/ui/vnc-jobs.c
@@ -53,7 +53,7 @@ struct VncJobQueue {
     QemuCond cond;
     QemuMutex mutex;
     QemuThread thread;
-    Buffer buffer;
+    QIOBuffer buffer;
     bool exit;
     QTAILQ_HEAD(, VncJob) jobs;
 };
@@ -166,7 +166,7 @@ void vnc_jobs_consume_buffer(VncState *vs)
     vnc_lock_output(vs);
     if (vs->jobs_buffer.offset) {
         vnc_write(vs, vs->jobs_buffer.buffer, vs->jobs_buffer.offset);
-        buffer_reset(&vs->jobs_buffer);
+        qio_buffer_reset(&vs->jobs_buffer);
     }
     flush = vs->csock != -1 && vs->abort != true;
     vnc_unlock_output(vs);
@@ -195,7 +195,7 @@ static void vnc_async_encoding_start(VncState *orig, VncState *local)
     local->output =  queue->buffer;
     local->csock = -1; /* Don't do any network work on this thread */
 
-    buffer_reset(&local->output);
+    qio_buffer_reset(&local->output);
 }
 
 static void vnc_async_encoding_end(VncState *orig, VncState *local)
@@ -272,10 +272,11 @@ static int vnc_worker_thread_loop(VncJobQueue *queue)
     vs.output.buffer[saved_offset + 1] = n_rectangles & 0xFF;
 
     vnc_lock_output(job->vs);
+
     if (job->vs->csock != -1) {
-        buffer_reserve(&job->vs->jobs_buffer, vs.output.offset);
-        buffer_append(&job->vs->jobs_buffer, vs.output.buffer,
-                      vs.output.offset);
+        qio_buffer_reserve(&job->vs->jobs_buffer, vs.output.offset);
+        qio_buffer_append(&job->vs->jobs_buffer, vs.output.buffer,
+                          vs.output.offset);
         /* Copy persistent encoding data */
         vnc_async_encoding_end(job->vs, &vs);
 
@@ -309,7 +310,7 @@ static void vnc_queue_clear(VncJobQueue *q)
 {
     qemu_cond_destroy(&queue->cond);
     qemu_mutex_destroy(&queue->mutex);
-    buffer_free(&queue->buffer);
+    qio_buffer_free(&queue->buffer);
     g_free(q);
     queue = NULL; /* Unset global queue */
 }
diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c
index 93a7b35..d3ca97e 100644
--- a/ui/vnc-ws.c
+++ b/ui/vnc-ws.c
@@ -85,8 +85,8 @@ void vncws_handshake_read(void *opaque)
     /* Typical HTTP headers from novnc are 512 bytes, so limiting
      * total header size to 4096 is easily enough. */
     size_t want = 4096 - vs->ws_input.offset;
-    buffer_reserve(&vs->ws_input, want);
-    ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), want);
+    qio_buffer_reserve(&vs->ws_input, want);
+    ret = vnc_client_read_buf(vs, qio_buffer_end(&vs->ws_input), want);
 
     if (!ret) {
         if (vs->csock == -1) {
@@ -101,7 +101,7 @@ void vncws_handshake_read(void *opaque)
     if (handshake_end) {
         qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
         vncws_process_handshake(vs, vs->ws_input.buffer, vs->ws_input.offset);
-        buffer_advance(&vs->ws_input, handshake_end - vs->ws_input.buffer +
+        qio_buffer_advance(&vs->ws_input, handshake_end - vs->ws_input.buffer +
                 strlen(WS_HANDSHAKE_END));
     } else if (vs->ws_input.offset >= 4096) {
         VNC_DEBUG("End of headers not found in first 4096 bytes\n");
@@ -117,8 +117,8 @@ long vnc_client_read_ws(VncState *vs)
     size_t payload_size, header_size;
     VNC_DEBUG("Read websocket %p size %zd offset %zd\n", vs->ws_input.buffer,
             vs->ws_input.capacity, vs->ws_input.offset);
-    buffer_reserve(&vs->ws_input, 4096);
-    ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), 4096);
+    qio_buffer_reserve(&vs->ws_input, 4096);
+    ret = vnc_client_read_buf(vs, qio_buffer_end(&vs->ws_input), 4096);
     if (!ret) {
         return 0;
     }
@@ -136,7 +136,7 @@ long vnc_client_read_ws(VncState *vs)
                 return err;
             }
 
-            buffer_advance(&vs->ws_input, header_size);
+            qio_buffer_advance(&vs->ws_input, header_size);
         }
         if (vs->ws_payload_remain != 0) {
             err = vncws_decode_frame_payload(&vs->ws_input,
@@ -152,10 +152,10 @@ long vnc_client_read_ws(VncState *vs)
             }
             ret += err;
 
-            buffer_reserve(&vs->input, payload_size);
-            buffer_append(&vs->input, payload, payload_size);
+            qio_buffer_reserve(&vs->input, payload_size);
+            qio_buffer_append(&vs->input, payload, payload_size);
 
-            buffer_advance(&vs->ws_input, payload_size);
+            qio_buffer_advance(&vs->ws_input, payload_size);
         }
     } while (vs->ws_input.offset > 0);
 
@@ -168,13 +168,13 @@ long vnc_client_write_ws(VncState *vs)
     VNC_DEBUG("Write WS: Pending output %p size %zd offset %zd\n",
               vs->output.buffer, vs->output.capacity, vs->output.offset);
     vncws_encode_frame(&vs->ws_output, vs->output.buffer, vs->output.offset);
-    buffer_reset(&vs->output);
+    qio_buffer_reset(&vs->output);
     ret = vnc_client_write_buf(vs, vs->ws_output.buffer, vs->ws_output.offset);
     if (!ret) {
         return 0;
     }
 
-    buffer_advance(&vs->ws_output, ret);
+    qio_buffer_advance(&vs->ws_output, ret);
 
     if (vs->ws_output.offset == 0) {
         qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
@@ -257,8 +257,8 @@ void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size)
     g_free(key);
 }
 
-void vncws_encode_frame(Buffer *output, const void *payload,
-        const size_t payload_size)
+void vncws_encode_frame(QIOBuffer *output, const void *payload,
+                        const size_t payload_size)
 {
     size_t header_size = 0;
     unsigned char opcode = WS_OPCODE_BINARY_FRAME;
@@ -285,12 +285,12 @@ void vncws_encode_frame(Buffer *output, const void *payload,
         header_size = 10;
     }
 
-    buffer_reserve(output, header_size + payload_size);
-    buffer_append(output, header.buf, header_size);
-    buffer_append(output, payload, payload_size);
+    qio_buffer_reserve(output, header_size + payload_size);
+    qio_buffer_append(output, header.buf, header_size);
+    qio_buffer_append(output, payload, payload_size);
 }
 
-int vncws_decode_frame_header(Buffer *input,
+int vncws_decode_frame_header(QIOBuffer *input,
                               size_t *header_size,
                               size_t *payload_remain,
                               WsMask *payload_mask)
@@ -344,7 +344,7 @@ int vncws_decode_frame_header(Buffer *input,
     return 1;
 }
 
-int vncws_decode_frame_payload(Buffer *input,
+int vncws_decode_frame_payload(QIOBuffer *input,
                                size_t *payload_remain, WsMask *payload_mask,
                                uint8_t **payload, size_t *payload_size)
 {
diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h
index 9494225..5437102 100644
--- a/ui/vnc-ws.h
+++ b/ui/vnc-ws.h
@@ -79,13 +79,13 @@ void vncws_handshake_read(void *opaque);
 long vnc_client_write_ws(VncState *vs);
 long vnc_client_read_ws(VncState *vs);
 void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size);
-void vncws_encode_frame(Buffer *output, const void *payload,
+void vncws_encode_frame(QIOBuffer *output, const void *payload,
             const size_t payload_size);
-int vncws_decode_frame_header(Buffer *input,
+int vncws_decode_frame_header(QIOBuffer *input,
                               size_t *header_size,
                               size_t *payload_remain,
                               WsMask *payload_mask);
-int vncws_decode_frame_payload(Buffer *input,
+int vncws_decode_frame_payload(QIOBuffer *input,
                                size_t *payload_remain, WsMask *payload_mask,
                                uint8_t **payload, size_t *payload_size);
 
diff --git a/ui/vnc.c b/ui/vnc.c
index f55776e..af1f792 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -651,49 +651,6 @@ void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h,
     vnc_write_s32(vs, encoding);
 }
 
-void buffer_reserve(Buffer *buffer, size_t len)
-{
-    if ((buffer->capacity - buffer->offset) < len) {
-        buffer->capacity += (len + 1024);
-        buffer->buffer = g_realloc(buffer->buffer, buffer->capacity);
-    }
-}
-
-static int buffer_empty(Buffer *buffer)
-{
-    return buffer->offset == 0;
-}
-
-uint8_t *buffer_end(Buffer *buffer)
-{
-    return buffer->buffer + buffer->offset;
-}
-
-void buffer_reset(Buffer *buffer)
-{
-        buffer->offset = 0;
-}
-
-void buffer_free(Buffer *buffer)
-{
-    g_free(buffer->buffer);
-    buffer->offset = 0;
-    buffer->capacity = 0;
-    buffer->buffer = NULL;
-}
-
-void buffer_append(Buffer *buffer, const void *data, size_t len)
-{
-    memcpy(buffer->buffer + buffer->offset, data, len);
-    buffer->offset += len;
-}
-
-void buffer_advance(Buffer *buf, size_t len)
-{
-    memmove(buf->buffer, buf->buffer + len,
-            (buf->offset - len));
-    buf->offset -= len;
-}
 
 static void vnc_desktop_resize(VncState *vs)
 {
@@ -1224,10 +1181,10 @@ void vnc_disconnect_finish(VncState *vs)
     vnc_lock_output(vs);
     vnc_qmp_event(vs, QAPI_EVENT_VNC_DISCONNECTED);
 
-    buffer_free(&vs->input);
-    buffer_free(&vs->output);
-    buffer_free(&vs->ws_input);
-    buffer_free(&vs->ws_output);
+    qio_buffer_free(&vs->input);
+    qio_buffer_free(&vs->output);
+    qio_buffer_free(&vs->ws_input);
+    qio_buffer_free(&vs->ws_output);
 
     qapi_free_VncClientInfo(vs->info);
 
@@ -1257,7 +1214,7 @@ void vnc_disconnect_finish(VncState *vs)
     if (vs->bh != NULL) {
         qemu_bh_delete(vs->bh);
     }
-    buffer_free(&vs->jobs_buffer);
+    qio_buffer_free(&vs->jobs_buffer);
 
     for (i = 0; i < VNC_STAT_ROWS; ++i) {
         g_free(vs->lossy_rect[i]);
@@ -1379,7 +1336,7 @@ static long vnc_client_write_plain(VncState *vs)
     if (!ret)
         return 0;
 
-    buffer_advance(&vs->output, ret);
+    qio_buffer_advance(&vs->output, ret);
 
     if (vs->output.offset == 0) {
         qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
@@ -1495,8 +1452,8 @@ static long vnc_client_read_plain(VncState *vs)
     int 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);
-    ret = vnc_client_read_buf(vs, buffer_end(&vs->input), 4096);
+    qio_buffer_reserve(&vs->input, 4096);
+    ret = vnc_client_read_buf(vs, qio_buffer_end(&vs->input), 4096);
     if (!ret)
         return 0;
     vs->input.offset += ret;
@@ -1554,7 +1511,7 @@ void vnc_client_read(void *opaque)
         }
 
         if (!ret) {
-            buffer_advance(&vs->input, len);
+            qio_buffer_advance(&vs->input, len);
         } else {
             vs->read_handler_expect = ret;
         }
@@ -1563,13 +1520,13 @@ void vnc_client_read(void *opaque)
 
 void vnc_write(VncState *vs, const void *data, size_t len)
 {
-    buffer_reserve(&vs->output, len);
+    qio_buffer_reserve(&vs->output, len);
 
-    if (vs->csock != -1 && buffer_empty(&vs->output)) {
+    if (vs->csock != -1 && qio_buffer_empty(&vs->output)) {
         qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, vnc_client_write, vs);
     }
 
-    buffer_append(&vs->output, data, len);
+    qio_buffer_append(&vs->output, data, len);
 }
 
 void vnc_write_s32(VncState *vs, int32_t value)
diff --git a/ui/vnc.h b/ui/vnc.h
index 814d720..f44a949 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -33,6 +33,7 @@
 #include "ui/console.h"
 #include "audio/audio.h"
 #include "qemu/bitmap.h"
+#include "io/buffer.h"
 #include <zlib.h>
 #include <stdbool.h>
 
@@ -55,13 +56,6 @@
  *
  *****************************************************************************/
 
-typedef struct Buffer
-{
-    size_t capacity;
-    size_t offset;
-    uint8_t *buffer;
-} Buffer;
-
 typedef struct VncState VncState;
 typedef struct VncJob VncJob;
 typedef struct VncRect VncRect;
@@ -194,15 +188,15 @@ typedef struct VncTight {
     uint8_t quality;
     uint8_t compression;
     uint8_t pixel24;
-    Buffer tight;
-    Buffer tmp;
-    Buffer zlib;
-    Buffer gradient;
+    QIOBuffer tight;
+    QIOBuffer tmp;
+    QIOBuffer zlib;
+    QIOBuffer gradient;
 #ifdef CONFIG_VNC_JPEG
-    Buffer jpeg;
+    QIOBuffer jpeg;
 #endif
 #ifdef CONFIG_VNC_PNG
-    Buffer png;
+    QIOBuffer png;
 #endif
     int levels[4];
     z_stream stream[4];
@@ -213,18 +207,18 @@ typedef struct VncHextile {
 } VncHextile;
 
 typedef struct VncZlib {
-    Buffer zlib;
-    Buffer tmp;
+    QIOBuffer zlib;
+    QIOBuffer tmp;
     z_stream stream;
     int level;
 } VncZlib;
 
 typedef struct VncZrle {
     int type;
-    Buffer fb;
-    Buffer zrle;
-    Buffer tmp;
-    Buffer zlib;
+    QIOBuffer fb;
+    QIOBuffer zrle;
+    QIOBuffer tmp;
+    QIOBuffer zlib;
     z_stream stream;
     VncPalette palette;
 } VncZrle;
@@ -295,10 +289,10 @@ struct VncState
 
     VncClientInfo *info;
 
-    Buffer output;
-    Buffer input;
-    Buffer ws_input;
-    Buffer ws_output;
+    QIOBuffer output;
+    QIOBuffer input;
+    QIOBuffer ws_input;
+    QIOBuffer ws_output;
     size_t ws_payload_remain;
     WsMask ws_payload_mask;
     /* current output mode information */
@@ -320,7 +314,7 @@ struct VncState
     bool initialized;
     QemuMutex output_mutex;
     QEMUBH *bh;
-    Buffer jobs_buffer;
+    QIOBuffer jobs_buffer;
 
     /* Encoding specific, if you add something here, don't forget to
      *  update vnc_async_encoding_start()
@@ -538,14 +532,6 @@ int vnc_client_io_error(VncState *vs, int ret, int last_errno);
 void start_client_init(VncState *vs);
 void start_auth_vnc(VncState *vs);
 
-/* Buffer management */
-void buffer_reserve(Buffer *buffer, size_t len);
-void buffer_reset(Buffer *buffer);
-void buffer_free(Buffer *buffer);
-void buffer_append(Buffer *buffer, const void *data, size_t len);
-void buffer_advance(Buffer *buf, size_t len);
-uint8_t *buffer_end(Buffer *buffer);
-
 
 /* Misc helpers */
 
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 28/34] io: add QIOChannelWebsock class
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (26 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 27/34] io: pull Buffer code out of VNC module Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 29/34] ui: convert VNC server to use QEMUIOChannelSocket classes Daniel P. Berrange
                   ` (6 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

Add a QIOChannel subclass that can run the websocket protocol over
the top of another QIOChannel instance.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 include/io/channel-websock.h | 108 +++++
 io/Makefile.objs             |   1 +
 io/channel-websock.c         | 976 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1085 insertions(+)
 create mode 100644 include/io/channel-websock.h
 create mode 100644 io/channel-websock.c

diff --git a/include/io/channel-websock.h b/include/io/channel-websock.h
new file mode 100644
index 0000000..4641921
--- /dev/null
+++ b/include/io/channel-websock.h
@@ -0,0 +1,108 @@
+/*
+ * QEMU I/O channels driver websockets
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QIO_CHANNEL_WEBSOCK_H__
+#define QIO_CHANNEL_WEBSOCK_H__
+
+#include "io/channel.h"
+#include "io/buffer.h"
+#include "io/task.h"
+
+#define TYPE_QIO_CHANNEL_WEBSOCK "qemu:io-channel-websock"
+#define QIO_CHANNEL_WEBSOCK(obj)                                     \
+    OBJECT_CHECK(QIOChannelWebsock, (obj), TYPE_QIO_CHANNEL_WEBSOCK)
+
+typedef struct QIOChannelWebsock QIOChannelWebsock;
+typedef union QIOChannelWebsockMask QIOChannelWebsockMask;
+
+union QIOChannelWebsockMask {
+    char c[4];
+    uint32_t u;
+};
+
+/**
+ * QIOChannelWebsock
+ *
+ * The QIOChannelWebsock class provides a channel wrapper which
+ * can transparently run the HTTP websockets protocol. This is
+ * usually used over a TCP socket, but there is actually no
+ * technical restriction on which type of master channel is
+ * used as the transport.
+ *
+ * This channel object is currently only capable of running as
+ * a websocket server and is a pretty crude implementation
+ * of it, not supporting the full websockets protocol feature
+ * set. It is sufficient to use with a simple websockets
+ * client for encapsulating VNC for noVNC in-browser client.
+ */
+
+struct QIOChannelWebsock {
+    QIOChannel parent;
+    QIOChannel *master;
+    QIOBuffer encinput;
+    QIOBuffer encoutput;
+    QIOBuffer rawinput;
+    QIOBuffer rawoutput;
+    size_t payload_remain;
+    QIOChannelWebsockMask mask;
+    guint io_tag;
+    Error *io_err;
+    gboolean io_eof;
+};
+
+/**
+ * qio_channel_websock_new_server:
+ * @master: the underlying channel object
+ *
+ * Create a new websockets channel that runs the server
+ * side of the protocol.
+ *
+ * After creating the channel, it is mandatory to call
+ * the qio_channel_websock_handshake() method before attempting
+ * todo any I/O on the channel.
+ *
+ * Once the handshake has completed, all I/O should be done
+ * via the new websocket channel object and not the original
+ * master channel
+ *
+ * Returns: the new websockets channel object
+ */
+QIOChannelWebsock *
+qio_channel_websock_new_server(QIOChannel *master);
+
+/**
+ * qio_channel_websock_handshake:
+ * @ioc: the websocket channel object
+ * @func: the callback to invoke when completed
+ * @opaque: opaque data to pass to @func
+ * @destroy: optional callback to free @opaque
+ *
+ * Perform the websocket handshake. This method
+ * will return immediately and the handshake will
+ * continue in the background, provided the main
+ * loop is running. When the handshake is complete,
+ * or fails, the @func callback will be invoked.
+ */
+void qio_channel_websock_handshake(QIOChannelWebsock *ioc,
+                                   QIOTaskFunc func,
+                                   gpointer opaque,
+                                   GDestroyNotify destroy);
+
+#endif /* QIO_CHANNEL_WEBSOCK_H__ */
diff --git a/io/Makefile.objs b/io/Makefile.objs
index b415753..76099bf 100644
--- a/io/Makefile.objs
+++ b/io/Makefile.objs
@@ -5,3 +5,4 @@ util-obj-y += channel-unix.o
 util-obj-y += channel-socket.o
 util-obj-y += channel-file.o
 util-obj-y += channel-tls.o
+util-obj-y += channel-websock.o
diff --git a/io/channel-websock.c b/io/channel-websock.c
new file mode 100644
index 0000000..57ae737
--- /dev/null
+++ b/io/channel-websock.c
@@ -0,0 +1,976 @@
+/*
+ * QEMU I/O channels driver websockets
+ *
+ * Copyright (c) 2015 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "io/channel-websock.h"
+#include "crypto/hash.h"
+
+#include <glib/gi18n.h>
+
+/* #define DEBUG_IOC */
+
+#ifdef DEBUG_IOC
+#define DPRINTF(fmt, ...) \
+    do { fprintf(stderr, "channel-websock: " fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) \
+    do { } while (0)
+#endif
+
+/* Max amount to allow in rawinput/rawoutput buffers */
+#define QIO_CHANNEL_WEBSOCK_MAX_BUFFER 8192
+
+#define B64LEN(__x) (((__x + 2) / 3) * 12 / 3)
+#define SHA1_DIGEST_LEN 20
+
+#define QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN 24
+#define QIO_CHANNEL_WEBSOCK_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+#define QIO_CHANNEL_WEBSOCK_GUID_LEN strlen(QIO_CHANNEL_WEBSOCK_GUID)
+
+#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RESPONSE  \
+    "HTTP/1.1 101 Switching Protocols\r\n"      \
+    "Upgrade: websocket\r\n"                    \
+    "Connection: Upgrade\r\n"                   \
+    "Sec-WebSocket-Accept: %s\r\n"              \
+    "Sec-WebSocket-Protocol: binary\r\n"        \
+    "\r\n"
+#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM "\r\n"
+#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_END "\r\n\r\n"
+#define QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION "13"
+
+#define QIO_CHANNEL_WEBSOCK_HEAD_MIN_LEN sizeof(uint16_t)
+#define QIO_CHANNEL_WEBSOCK_HEAD_MAX_LEN \
+    (QIO_CHANNEL_WEBSOCK_HEAD_MIN_LEN + sizeof(uint64_t) + sizeof(uint32_t))
+
+typedef struct QIOChannelWebsockHeader QIOChannelWebsockHeader;
+
+struct QEMU_PACKED QIOChannelWebsockHeader {
+    unsigned char b0;
+    unsigned char b1;
+    union {
+        struct QEMU_PACKED {
+            uint16_t l16;
+            QIOChannelWebsockMask m16;
+        } s16;
+        struct QEMU_PACKED {
+            uint64_t l64;
+            QIOChannelWebsockMask m64;
+        } s64;
+        QIOChannelWebsockMask m;
+    } u;
+};
+
+enum {
+    QIO_CHANNEL_WEBSOCK_OPCODE_CONTINUATION = 0x0,
+    QIO_CHANNEL_WEBSOCK_OPCODE_TEXT_FRAME = 0x1,
+    QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME = 0x2,
+    QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE = 0x8,
+    QIO_CHANNEL_WEBSOCK_OPCODE_PING = 0x9,
+    QIO_CHANNEL_WEBSOCK_OPCODE_PONG = 0xA
+};
+
+static char *qio_channel_websock_handshake_entry(const char *handshake,
+                                                 size_t handshake_len,
+                                                 const char *name)
+{
+    char *begin, *end, *ret = NULL;
+    char *line = g_strdup_printf("%s%s: ",
+                                 QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM,
+                                 name);
+    begin = g_strstr_len(handshake, handshake_len, line);
+    if (begin != NULL) {
+        begin += strlen(line);
+        end = g_strstr_len(begin, handshake_len - (begin - handshake),
+                QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM);
+        if (end != NULL) {
+            ret = g_strndup(begin, end - begin);
+        }
+    }
+    g_free(line);
+    return ret;
+}
+
+
+static int qio_channel_websock_handshake_send_response(QIOChannelWebsock *ioc,
+                                                       const char *key,
+                                                       Error **errp)
+{
+    char combined_key[QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN +
+                      QIO_CHANNEL_WEBSOCK_GUID_LEN + 1];
+    char *accept = NULL, *response = NULL;
+    size_t responselen;
+
+    g_strlcpy(combined_key, key, QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN + 1);
+    g_strlcat(combined_key, QIO_CHANNEL_WEBSOCK_GUID,
+              QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN +
+              QIO_CHANNEL_WEBSOCK_GUID_LEN + 1);
+
+    /* hash and encode it */
+    if (qcrypto_hash_base64(QCRYPTO_HASH_ALG_SHA1,
+                            combined_key,
+                            QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN +
+                            QIO_CHANNEL_WEBSOCK_GUID_LEN,
+                            &accept,
+                            errp) < 0) {
+        return -1;
+    }
+
+    response = g_strdup_printf(QIO_CHANNEL_WEBSOCK_HANDSHAKE_RESPONSE, accept);
+    responselen = strlen(response);
+    qio_buffer_reserve(&ioc->encoutput, responselen);
+    qio_buffer_append(&ioc->encoutput, response, responselen);
+
+    g_free(accept);
+    g_free(response);
+
+    return 0;
+}
+
+static int qio_channel_websock_handshake_process(QIOChannelWebsock *ioc,
+                                                 const char *line,
+                                                 size_t size,
+                                                 Error **errp)
+{
+    int ret = -1;
+    char *protocols = qio_channel_websock_handshake_entry(line, size,
+            "Sec-WebSocket-Protocol");
+    char *version = qio_channel_websock_handshake_entry(line, size,
+            "Sec-WebSocket-Version");
+    char *key = qio_channel_websock_handshake_entry(line, size,
+            "Sec-WebSocket-Key");
+
+    if (!protocols) {
+        error_setg(errp, "%s", _("Missing websocket protocol header data"));
+        goto cleanup;
+    }
+
+    if (!version) {
+        error_setg(errp, "%s", _("Missing websocket version header data"));
+        goto cleanup;
+    }
+
+    if (!key) {
+        error_setg(errp, "%s", _("Missing websocket key header data"));
+        goto cleanup;
+    }
+
+    if (!g_strrstr(protocols, "binary")) {
+        error_setg(errp, _("No 'binary' protocol is supported by client '%s'"),
+                   protocols);
+        goto cleanup;
+    }
+
+    if (!g_str_equal(version, QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION)) {
+        error_setg(errp, _("Version '%s' is not supported by client '%s'"),
+                   QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION, version);
+        goto cleanup;
+    }
+
+    if (strlen(key) != QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN) {
+        error_setg(errp, _("Key length '%zu' was not as expected '%d'"),
+                   strlen(key), QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN);
+        goto cleanup;
+    }
+
+    ret = qio_channel_websock_handshake_send_response(ioc, key, errp);
+
+ cleanup:
+    g_free(protocols);
+    g_free(version);
+    g_free(key);
+    return ret;
+}
+
+static int qio_channel_websock_handshake_read(QIOChannelWebsock *ioc,
+                                              Error **errp)
+{
+    char *handshake_end;
+    ssize_t ret;
+    /* Typical HTTP headers from novnc are 512 bytes, so limiting
+     * total header size to 4096 is easily enough. */
+    size_t want = 4096 - ioc->encinput.offset;
+    qio_buffer_reserve(&ioc->encinput, want);
+    ret = qio_channel_read(ioc->master,
+                           (char *)qio_buffer_end(&ioc->encinput), want, errp);
+    if (ret < 0) {
+        return -1;
+    }
+    ioc->encinput.offset += ret;
+
+    handshake_end = g_strstr_len((char *)ioc->encinput.buffer,
+                                 ioc->encinput.offset,
+                                 QIO_CHANNEL_WEBSOCK_HANDSHAKE_END);
+    if (!handshake_end) {
+        if (ioc->encinput.offset >= 4096) {
+            error_setg(errp, "%s",
+                       _("End of headers not found in first 4096 bytes"));
+            return -1;
+        } else {
+            return 0;
+        }
+    }
+
+    if (qio_channel_websock_handshake_process(ioc,
+                                              (char *)ioc->encinput.buffer,
+                                              ioc->encinput.offset,
+                                              errp) < 0) {
+        return -1;
+    }
+
+    qio_buffer_advance(&ioc->encinput,
+                       handshake_end - (char *)ioc->encinput.buffer +
+                       strlen(QIO_CHANNEL_WEBSOCK_HANDSHAKE_END));
+    return 1;
+}
+
+static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc,
+                                                   GIOCondition condition,
+                                                   gpointer user_data)
+{
+    QIOTask *task = QIO_TASK(user_data);
+    QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(task->source);
+    Error *err = NULL;
+    ssize_t ret;
+
+    DPRINTF("Sending websock handshake reply %p\n", wioc);
+    ret = qio_channel_write(wioc->master,
+                            (char *)wioc->encoutput.buffer,
+                            wioc->encoutput.offset,
+                            &err);
+
+    if (ret < 0) {
+        qio_task_abort(task, err);
+        DPRINTF("Error sending websock handshake reply %p: %s\n",
+                wioc, error_get_pretty(err));
+        error_free(err);
+        return FALSE;
+    }
+
+    qio_buffer_advance(&wioc->encoutput, ret);
+    if (wioc->encoutput.offset == 0) {
+        DPRINTF("Finished sending websock handshake %p\n",
+                wioc);
+        qio_task_complete(task);
+        return FALSE;
+    }
+    return TRUE;
+}
+
+static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc,
+                                                 GIOCondition condition,
+                                                 gpointer user_data)
+{
+    QIOTask *task = QIO_TASK(user_data);
+    QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(task->source);
+    Error *err = NULL;
+    int ret;
+
+    DPRINTF("Reading websock handshake request %p\n", wioc);
+    ret = qio_channel_websock_handshake_read(wioc, &err);
+    if (ret < 0) {
+        DPRINTF("Error reading websock handshake %s\n",
+                error_get_pretty(err));
+        qio_task_abort(task, err);
+        error_free(err);
+        return FALSE;
+    }
+    if (ret == 0) {
+        DPRINTF("Blocking on more request data\n");
+        /* need more data still */
+        return TRUE;
+    }
+
+    DPRINTF("Websock request complete, adding watch for reply %p\n",
+            wioc);
+
+    object_ref(OBJECT(task));
+    qio_channel_add_watch_full(
+        wioc->master,
+        G_PRIORITY_DEFAULT,
+        G_IO_OUT,
+        qio_channel_websock_handshake_send,
+        task,
+        (GDestroyNotify)object_unref);
+    return FALSE;
+}
+
+
+static void qio_channel_websock_encode(QIOChannelWebsock *ioc)
+{
+    size_t header_size = 0;
+    unsigned char opcode = QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME;
+    union {
+        char buf[QIO_CHANNEL_WEBSOCK_HEAD_MAX_LEN];
+        QIOChannelWebsockHeader ws;
+    } header;
+
+    DPRINTF("Encoding pending raw output %zu %p\n",
+            ioc->rawoutput.offset, ioc);
+    if (!ioc->rawoutput.offset) {
+        return;
+    }
+
+    header.ws.b0 = 0x80 | (opcode & 0x0f);
+    if (ioc->rawoutput.offset <= 125) {
+        header.ws.b1 = (uint8_t)ioc->rawoutput.offset;
+        header_size = 2;
+    } else if (ioc->rawoutput.offset < 65536) {
+        header.ws.b1 = 0x7e;
+        header.ws.u.s16.l16 = cpu_to_be16((uint16_t)ioc->rawoutput.offset);
+        header_size = 4;
+    } else {
+        header.ws.b1 = 0x7f;
+        header.ws.u.s64.l64 = cpu_to_be64(ioc->rawoutput.offset);
+        header_size = 10;
+    }
+
+    qio_buffer_reserve(&ioc->encoutput, header_size + ioc->rawoutput.offset);
+    qio_buffer_append(&ioc->encoutput, header.buf, header_size);
+    qio_buffer_append(&ioc->encoutput, ioc->rawoutput.buffer,
+                      ioc->rawoutput.offset);
+    qio_buffer_reset(&ioc->rawoutput);
+    DPRINTF("Have %zu bytes encoded output %p\n",
+            ioc->encoutput.offset, ioc);
+}
+
+
+static ssize_t qio_channel_websock_decode_header(QIOChannelWebsock *ioc,
+                                                 Error **errp)
+{
+    unsigned char opcode = 0, fin = 0, has_mask = 0;
+    size_t header_size = 0;
+    size_t payload_len;
+    QIOChannelWebsockHeader *header =
+        (QIOChannelWebsockHeader *)ioc->encinput.buffer;
+
+    if (ioc->payload_remain) {
+        error_setg(errp,
+                   _("Decoding header but %zu bytes of payload remain"),
+                   ioc->payload_remain);
+        return -1;
+    }
+    if (ioc->encinput.offset < QIO_CHANNEL_WEBSOCK_HEAD_MIN_LEN + 4) {
+        /* header not complete */
+        return QIO_CHANNEL_ERR_BLOCK;
+    }
+
+    fin = (header->b0 & 0x80) >> 7;
+    opcode = header->b0 & 0x0f;
+    has_mask = (header->b1 & 0x80) >> 7;
+    payload_len = header->b1 & 0x7f;
+
+    if (opcode == QIO_CHANNEL_WEBSOCK_OPCODE_CLOSE) {
+        /* disconnect */
+        return 0;
+    }
+
+    /* Websocket frame sanity check:
+     * * Websocket fragmentation is not supported.
+     * * All  websockets frames sent by a client have to be masked.
+     * * Only binary encoding is supported.
+     */
+    if (!fin) {
+        error_setg(errp, "%s", _("websocket fragmentation is not supported"));
+        return -1;
+    }
+    if (!has_mask) {
+        error_setg(errp, "%s", _("websocket frames must be masked"));
+        return -1;
+    }
+    if (opcode != QIO_CHANNEL_WEBSOCK_OPCODE_BINARY_FRAME) {
+        error_setg(errp, "%s", _("only binary websocket frames are supported"));
+        return -1;
+    }
+
+    if (payload_len < 126) {
+        ioc->payload_remain = payload_len;
+        header_size = 6;
+        ioc->mask = header->u.m;
+    } else if (payload_len == 126 && ioc->encinput.offset >= 8) {
+        ioc->payload_remain = be16_to_cpu(header->u.s16.l16);
+        header_size = 8;
+        ioc->mask = header->u.s16.m16;
+    } else if (payload_len == 127 && ioc->encinput.offset >= 14) {
+        ioc->payload_remain = be64_to_cpu(header->u.s64.l64);
+        header_size = 14;
+        ioc->mask = header->u.s64.m64;
+    } else {
+        /* header not complete */
+        return QIO_CHANNEL_ERR_BLOCK;
+    }
+
+    qio_buffer_advance(&ioc->encinput, header_size);
+    return 1;
+}
+
+
+static ssize_t qio_channel_websock_decode_payload(QIOChannelWebsock *ioc,
+                                                  Error **errp)
+{
+    size_t i;
+    size_t payload_len;
+    uint32_t *payload32;
+
+    if (!ioc->payload_remain) {
+        error_setg(errp, "%s",
+                   _("Decoding payload but no bytes of payload remain"));
+        return -1;
+    }
+
+    /* If we aren't at the end of the payload, then drop
+     * off the last bytes, so we're always multiple of 4
+     * for purpose of unmasking, except at end of payload
+     */
+    if (ioc->encinput.offset < ioc->payload_remain) {
+        payload_len = ioc->encinput.offset - (ioc->encinput.offset % 4);
+    } else {
+        payload_len = ioc->payload_remain;
+    }
+    if (payload_len == 0) {
+        return QIO_CHANNEL_ERR_BLOCK;
+    }
+
+    ioc->payload_remain -= payload_len;
+
+    /* unmask frame */
+    /* process 1 frame (32 bit op) */
+    payload32 = (uint32_t *)ioc->encinput.buffer;
+    for (i = 0; i < payload_len / 4; i++) {
+        payload32[i] ^= ioc->mask.u;
+    }
+    /* process the remaining bytes (if any) */
+    for (i *= 4; i < payload_len; i++) {
+        ioc->encinput.buffer[i] ^= ioc->mask.c[i % 4];
+    }
+
+    qio_buffer_reserve(&ioc->rawinput, payload_len);
+    qio_buffer_append(&ioc->rawinput, ioc->encinput.buffer, payload_len);
+    qio_buffer_advance(&ioc->encinput, payload_len);
+    return payload_len;
+}
+
+
+QIOChannelWebsock *
+qio_channel_websock_new_server(QIOChannel *master)
+{
+    QIOChannelWebsock *wioc;
+
+    wioc = QIO_CHANNEL_WEBSOCK(object_new(TYPE_QIO_CHANNEL_WEBSOCK));
+
+    wioc->master = master;
+    object_ref(OBJECT(master));
+
+    return wioc;
+}
+
+void qio_channel_websock_handshake(QIOChannelWebsock *ioc,
+                                   QIOTaskFunc func,
+                                   gpointer opaque,
+                                   GDestroyNotify destroy)
+{
+    QIOTask *task;
+
+    task = qio_task_new(OBJECT(ioc),
+                        func,
+                        opaque,
+                        destroy);
+
+    DPRINTF("Adding watch on master %p for websocket %p handshake\n",
+            ioc->master, ioc);
+    qio_channel_add_watch_full(ioc->master,
+                               G_PRIORITY_DEFAULT,
+                               G_IO_IN,
+                               qio_channel_websock_handshake_io,
+                               task,
+                               (GDestroyNotify)object_unref);
+}
+
+static void qio_channel_websock_init(Object *obj G_GNUC_UNUSED)
+{
+}
+
+
+static void qio_channel_websock_finalize(Object *obj)
+{
+    QIOChannelWebsock *ioc = QIO_CHANNEL_WEBSOCK(obj);
+
+    qio_buffer_free(&ioc->encinput);
+    qio_buffer_free(&ioc->encoutput);
+    qio_buffer_free(&ioc->rawinput);
+    qio_buffer_free(&ioc->rawoutput);
+    object_unref(OBJECT(ioc->master));
+    if (ioc->io_tag) {
+        g_source_remove(ioc->io_tag);
+    }
+    if (ioc->io_err) {
+        error_free(ioc->io_err);
+    }
+}
+
+
+static ssize_t qio_channel_websock_read_wire(QIOChannelWebsock *ioc,
+                                             Error **errp)
+{
+    ssize_t ret;
+
+    DPRINTF("Have %zu bytes %p\n", ioc->encoutput.offset, ioc);
+    if (ioc->encinput.offset < 4096) {
+        size_t want = 4096 - ioc->encinput.offset;
+
+        qio_buffer_reserve(&ioc->encinput, want);
+        ret = qio_channel_read(ioc->master,
+                               (char *)ioc->encinput.buffer +
+                               ioc->encinput.offset,
+                               want,
+                               errp);
+        if (ret < 0) {
+            return ret;
+        }
+        if (ret == 0 &&
+            ioc->encinput.offset == 0) {
+            DPRINTF("EOF on wire & no more enc data availabl\n");
+            return 0;
+        }
+        ioc->encinput.offset += ret;
+        DPRINTF("Now have %zu bytes enc input\n", ioc->encinput.offset);
+    }
+
+    if (ioc->payload_remain == 0) {
+        DPRINTF("Looking to decode header\n");
+        ret = qio_channel_websock_decode_header(ioc, errp);
+        if (ret < 0) {
+            return ret;
+        }
+        if (ret == 0) {
+            DPRINTF("EOF when decoding header\n");
+            return 0;
+        }
+    }
+    DPRINTF("Looking to decode payload %zu\n", ioc->payload_remain);
+
+    ret = qio_channel_websock_decode_payload(ioc, errp);
+    if (ret < 0) {
+        return ret;
+    }
+    DPRINTF("Now have %zu bytes raw input\n", ioc->rawinput.offset);
+    return ret;
+}
+
+
+static ssize_t qio_channel_websock_write_wire(QIOChannelWebsock *ioc,
+                                              Error **errp)
+{
+    ssize_t ret;
+    ssize_t done = 0;
+    qio_channel_websock_encode(ioc);
+
+    DPRINTF("Writing %zu bytes %p\n", ioc->encoutput.offset, ioc);
+    while (ioc->encoutput.offset > 0) {
+        ret = qio_channel_write(ioc->master,
+                                (char *)ioc->encoutput.buffer,
+                                ioc->encoutput.offset,
+                                errp);
+        if (ret < 0) {
+            if (ret == QIO_CHANNEL_ERR_BLOCK &&
+                done > 0) {
+                DPRINTF("Blocking but wrote %zu\n", done);
+                return done;
+            } else {
+                DPRINTF("Error while writing %s\n",
+                        error_get_pretty(*errp));
+                return ret;
+            }
+        }
+        qio_buffer_advance(&ioc->encoutput, ret);
+        done += ret;
+    }
+    DPRINTF("Wrote %zu total\n", done);
+    return done;
+}
+
+
+static void qio_channel_websock_flush_free(gpointer user_data)
+{
+    QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(user_data);
+    object_unref(OBJECT(wioc));
+}
+
+static void qio_channel_websock_set_watch(QIOChannelWebsock *ioc);
+
+static gboolean qio_channel_websock_flush(QIOChannel *ioc,
+                                          GIOCondition condition,
+                                          gpointer user_data)
+{
+    QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(user_data);
+    ssize_t ret;
+
+    DPRINTF("Websock master flush %p %d\n", ioc, condition);
+    if (condition & G_IO_OUT) {
+        ret = qio_channel_websock_write_wire(wioc, &wioc->io_err);
+        if (ret < 0) {
+            goto cleanup;
+        }
+    }
+
+    if (condition & G_IO_IN) {
+        ret = qio_channel_websock_read_wire(wioc, &wioc->io_err);
+        if (ret < 0) {
+            goto cleanup;
+        }
+        if (ret == 0) {
+            DPRINTF("Got EOF when reading %p\n", wioc);
+            wioc->io_eof = TRUE;
+        }
+    }
+
+ cleanup:
+    qio_channel_websock_set_watch(wioc);
+    return FALSE;
+}
+
+
+static void qio_channel_websock_unset_watch(QIOChannelWebsock *ioc)
+{
+    if (ioc->io_tag) {
+        DPRINTF("Removing old master watch %u %p\n", ioc->io_tag, ioc);
+        g_source_remove(ioc->io_tag);
+        ioc->io_tag = 0;
+    }
+}
+
+static void qio_channel_websock_set_watch(QIOChannelWebsock *ioc)
+{
+    GIOCondition cond = 0;
+
+    qio_channel_websock_unset_watch(ioc);
+
+    if (ioc->io_err) {
+        DPRINTF("Not adding master watch due to error %p %s\n",
+                ioc, error_get_pretty(ioc->io_err));
+        return;
+    }
+
+    if (ioc->encoutput.offset) {
+        cond |= G_IO_OUT;
+    }
+    if (ioc->encinput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER &&
+        !ioc->io_eof) {
+        cond |= G_IO_IN;
+    }
+
+    DPRINTF("Cond %d output=%zu input=%zu eof=%d\n",
+            cond, ioc->encoutput.offset, ioc->encinput.offset, ioc->io_eof);
+    if (cond) {
+        object_ref(OBJECT(ioc));
+        ioc->io_tag =
+            qio_channel_add_watch_full(ioc->master,
+                                       G_PRIORITY_DEFAULT,
+                                       cond,
+                                       qio_channel_websock_flush,
+                                       ioc,
+                                       qio_channel_websock_flush_free);
+    }
+}
+
+
+static ssize_t qio_channel_websock_readv(QIOChannel *ioc,
+                                         const struct iovec *iov,
+                                         size_t niov,
+                                         int **fds,
+                                         size_t *nfds,
+                                         int flags,
+                                         Error **errp)
+{
+    QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
+    size_t i;
+    ssize_t got = 0;
+    ssize_t ret;
+    DPRINTF("Read ioc %p %zu %p\n", iov, niov, ioc);
+    if (fds || nfds) {
+        error_setg(errp, "%s",
+                   _("Cannot receive file descriptors over websocket channel"));
+        return -1;
+    }
+    if (flags) {
+        error_setg(errp, "Flags %x not supported in websocket channel", flags);
+        return  -1;
+    }
+
+    if (wioc->io_err) {
+        *errp = error_copy(wioc->io_err);
+        return -1;
+    }
+
+    if (!wioc->rawinput.offset) {
+        ret = qio_channel_websock_read_wire(QIO_CHANNEL_WEBSOCK(ioc), errp);
+        if (ret < 0) {
+            return ret;
+        }
+    }
+
+    for (i = 0 ; i < niov ; i++) {
+        size_t want = iov[i].iov_len;
+        if (want > (wioc->rawinput.offset - got)) {
+            want = (wioc->rawinput.offset - got);
+        }
+
+        memcpy(iov[i].iov_base,
+               wioc->rawinput.buffer + got,
+               want);
+        got += want;
+
+        if (want < iov[i].iov_len) {
+            break;
+        }
+    }
+
+    qio_buffer_advance(&wioc->rawinput, got);
+    qio_channel_websock_set_watch(wioc);
+    DPRINTF("Returning %zu\n", got);
+    return got;
+}
+
+
+static ssize_t qio_channel_websock_writev(QIOChannel *ioc,
+                                          const struct iovec *iov,
+                                          size_t niov,
+                                          int *fds,
+                                          size_t nfds,
+                                          int flags,
+                                          Error **errp)
+{
+    QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
+    size_t i;
+    ssize_t done = 0;
+    ssize_t ret;
+
+    if (fds || nfds) {
+        error_setg(errp, "%s",
+                   _("Cannot send file descriptors over websocket channel"));
+        return -1;
+    }
+    if (flags) {
+        error_setg(errp, "Flags %x not supported in websocket channel", flags);
+        return  -1;
+    }
+
+    DPRINTF("Writev %p %zu %p err=%p\n", iov, niov, ioc, wioc->io_err);
+    if (wioc->io_err) {
+        *errp = error_copy(wioc->io_err);
+        return -1;
+    }
+
+    if (wioc->io_eof) {
+        error_setg(errp, "%s", "Broken pipe");
+        return -1;
+    }
+
+    for (i = 0; i < niov; i++) {
+        size_t want = iov[i].iov_len;
+        if ((want + wioc->rawoutput.offset) > QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
+            want = (QIO_CHANNEL_WEBSOCK_MAX_BUFFER - wioc->rawoutput.offset);
+        }
+        if (want == 0) {
+            goto done;
+        }
+
+        qio_buffer_reserve(&wioc->rawoutput, want);
+        qio_buffer_append(&wioc->rawoutput, iov[i].iov_base, want);
+        done += want;
+        if (want < iov[i].iov_len) {
+            break;
+        }
+    }
+
+ done:
+    ret = qio_channel_websock_write_wire(wioc, errp);
+    if (ret < 0 &&
+        ret != QIO_CHANNEL_ERR_BLOCK) {
+        qio_channel_websock_unset_watch(wioc);
+        return -1;
+    }
+
+    qio_channel_websock_set_watch(wioc);
+
+    if (done == 0) {
+        return QIO_CHANNEL_ERR_BLOCK;
+    }
+
+    return done;
+}
+
+static void qio_channel_websock_set_blocking(QIOChannel *ioc,
+                                             bool enabled)
+{
+    QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
+
+    qio_channel_set_blocking(wioc->master, enabled);
+}
+
+static int qio_channel_websock_close(QIOChannel *ioc,
+                                     Error **errp)
+{
+    QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
+
+    return qio_channel_close(wioc->master, errp);
+}
+
+typedef struct QIOChannelWebsockSource QIOChannelWebsockSource;
+struct QIOChannelWebsockSource {
+    GSource parent;
+    QIOChannelWebsock *wioc;
+    GIOCondition condition;
+};
+
+static gboolean
+qio_channel_websock_source_prepare(GSource *source,
+                                   gint *timeout)
+{
+    QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source;
+    GIOCondition cond = 0;
+    *timeout = -1;
+
+    if (wsource->wioc->rawinput.offset) {
+        cond |= G_IO_IN;
+    }
+    if (wsource->wioc->rawoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
+        cond |= G_IO_OUT;
+    }
+
+#if 0
+    DPRINTF("Prep source %d cond %d input=%zu output=%zu\n",
+            wsource->condition, cond,
+            wsource->wioc->rawinput.offset,
+            wsource->wioc->rawoutput.offset);
+#endif
+
+    return cond & wsource->condition;
+}
+
+static gboolean
+qio_channel_websock_source_check(GSource *source)
+{
+    QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source;
+    GIOCondition cond = 0;
+
+    if (wsource->wioc->rawinput.offset) {
+        cond |= G_IO_IN;
+    }
+    if (wsource->wioc->rawoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
+        cond |= G_IO_OUT;
+    }
+
+    if (cond & wsource->condition) {
+        DPRINTF("Check source %d cond %d input=%zu output=%zu\n",
+                wsource->condition, cond,
+                wsource->wioc->rawinput.offset,
+                wsource->wioc->rawoutput.offset);
+    }
+    return cond & wsource->condition;
+}
+
+static gboolean
+qio_channel_websock_source_dispatch(GSource *source,
+                                    GSourceFunc callback,
+                                    gpointer user_data)
+{
+    QIOChannelFunc func = (QIOChannelFunc)callback;
+    QIOChannelWebsockSource *wsource = (QIOChannelWebsockSource *)source;
+    GIOCondition cond = 0;
+
+    if (wsource->wioc->rawinput.offset) {
+        cond |= G_IO_IN;
+    }
+    if (wsource->wioc->rawoutput.offset < QIO_CHANNEL_WEBSOCK_MAX_BUFFER) {
+        cond |= G_IO_OUT;
+    }
+
+    DPRINTF("Disp source %d cond %d input=%zu output=%zu\n",
+            wsource->condition, cond,
+            wsource->wioc->rawinput.offset,
+            wsource->wioc->rawoutput.offset);
+    return (*func)(QIO_CHANNEL(wsource->wioc),
+                   (cond & wsource->condition),
+                   user_data);
+}
+
+static void
+qio_channel_websock_source_finalize(GSource *source)
+{
+    QIOChannelWebsockSource *ssource = (QIOChannelWebsockSource *)source;
+
+    object_unref(OBJECT(ssource->wioc));
+}
+
+GSourceFuncs qio_channel_websock_source_funcs = {
+    qio_channel_websock_source_prepare,
+    qio_channel_websock_source_check,
+    qio_channel_websock_source_dispatch,
+    qio_channel_websock_source_finalize
+};
+
+static GSource *qio_channel_websock_create_watch(QIOChannel *ioc,
+                                                 GIOCondition condition)
+{
+    QIOChannelWebsock *wioc = QIO_CHANNEL_WEBSOCK(ioc);
+    QIOChannelWebsockSource *ssource;
+    GSource *source;
+
+    DPRINTF("Creating websock watch %p cond=%d\n", wioc, condition);
+    source = g_source_new(&qio_channel_websock_source_funcs,
+                          sizeof(QIOChannelWebsockSource));
+    g_source_set_name(source, "QIOChannelWebsock");
+    ssource = (QIOChannelWebsockSource *)source;
+
+    ssource->wioc = wioc;
+    object_ref(OBJECT(wioc));
+
+    ssource->condition = condition;
+
+    qio_channel_websock_set_watch(wioc);
+    return source;
+}
+
+static void qio_channel_websock_class_init(ObjectClass *klass,
+                                           void *class_data G_GNUC_UNUSED)
+{
+    QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass);
+
+    ioc_klass->io_writev = qio_channel_websock_writev;
+    ioc_klass->io_readv = qio_channel_websock_readv;
+    ioc_klass->io_set_blocking = qio_channel_websock_set_blocking;
+    ioc_klass->io_close = qio_channel_websock_close;
+    ioc_klass->io_create_watch = qio_channel_websock_create_watch;
+}
+
+static const TypeInfo qio_channel_websock_info = {
+    .parent = TYPE_QIO_CHANNEL,
+    .name = TYPE_QIO_CHANNEL_WEBSOCK,
+    .instance_size = sizeof(QIOChannelWebsock),
+    .instance_init = qio_channel_websock_init,
+    .instance_finalize = qio_channel_websock_finalize,
+    .class_init = qio_channel_websock_class_init,
+};
+
+static void qio_channel_websock_register_types(void)
+{
+    type_register_static(&qio_channel_websock_info);
+}
+
+type_init(qio_channel_websock_register_types);
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 29/34] ui: convert VNC server to use QEMUIOChannelSocket classes
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (27 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 28/34] io: add QIOChannelWebsock class Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 30/34] ui: convert VNC server to use QIOChannelTLS Daniel P. Berrange
                   ` (5 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

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

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 ui/vnc-auth-sasl.c     |  39 ++--
 ui/vnc-auth-vencrypt.c |  45 ++--
 ui/vnc-jobs.c          |  12 +-
 ui/vnc-tls.c           |  24 +--
 ui/vnc-ws.c            |  53 +++--
 ui/vnc-ws.h            |   8 +-
 ui/vnc.c               | 545 +++++++++++++++++++++++++------------------------
 ui/vnc.h               |  33 ++-
 8 files changed, 422 insertions(+), 337 deletions(-)

diff --git a/ui/vnc-auth-sasl.c b/ui/vnc-auth-sasl.c
index 9733dfc..7851037 100644
--- a/ui/vnc-auth-sasl.c
+++ b/ui/vnc-auth-sasl.c
@@ -62,7 +62,7 @@ long vnc_client_write_sasl(VncState *vs)
                           (const char **)&vs->sasl.encoded,
                           &vs->sasl.encodedLength);
         if (err != SASL_OK)
-            return vnc_client_io_error(vs, -1, EIO);
+            return vnc_client_io_error(vs, -1, NULL);
 
         vs->sasl.encodedOffset = 0;
     }
@@ -86,7 +86,11 @@ long vnc_client_write_sasl(VncState *vs)
      * SASL encoded output
      */
     if (vs->output.offset == 0) {
-        qemu_set_fd_handler2(vs->csock, NULL, 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);
     }
 
     return ret;
@@ -110,7 +114,7 @@ long vnc_client_read_sasl(VncState *vs)
                       &decoded, &decodedLen);
 
     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",
               encoded, ret, decoded, decodedLen);
     qio_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);
     } else {
         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;
         }
 
         /* Check username whitelist ACL */
         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;
         }
 
-        VNC_DEBUG("Authentication successful %d\n", vs->csock);
+        VNC_DEBUG("Authentication successful %p\n", vs->ioc);
         vnc_write_u32(vs, 0); /* Accept auth */
         /*
          * 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);
     } else {
         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;
         }
 
         /* Check username whitelist ACL */
         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;
         }
 
-        VNC_DEBUG("Authentication successful %d\n", vs->csock);
+        VNC_DEBUG("Authentication successful %p\n", vs->ioc);
         vnc_write_u32(vs, 0); /* Accept auth */
         start_client_init(vs);
     }
@@ -495,13 +499,16 @@ void start_auth_sasl(VncState *vs)
     char *localAddr, *remoteAddr;
     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 */
-    if (!(localAddr = vnc_socket_local_addr("%s;%s", vs->csock)))
+    localAddr = vnc_socket_local_addr(vs->sioc, "%s;%s", NULL);
+    if (!localAddr) {
         goto authabort;
+    }
 
-    if (!(remoteAddr = vnc_socket_remote_addr("%s;%s", vs->csock))) {
+    remoteAddr = vnc_socket_remote_addr(vs->sioc, "%s;%s", NULL);
+    if (!remoteAddr) {
         g_free(localAddr);
         goto authabort;
     }
@@ -533,7 +540,8 @@ void start_auth_sasl(VncState *vs)
         sasl_ssf_t ssf;
 
         cipher = gnutls_cipher_get(vs->tls.session);
-        if (!(ssf = (sasl_ssf_t)gnutls_cipher_get_key_size(cipher))) {
+        ssf = (sasl_ssf_t)gnutls_cipher_get_key_size(cipher);
+        if (!ssf) {
             VNC_DEBUG("%s", "cannot TLS get cipher size\n");
             sasl_dispose(&vs->sasl.conn);
             vs->sasl.conn = NULL;
@@ -549,9 +557,12 @@ void start_auth_sasl(VncState *vs)
             vs->sasl.conn = NULL;
             goto authabort;
         }
-    } else
+    } else {
 #endif /* CONFIG_VNC_TLS */
         vs->sasl.wantSSF = 1;
+#ifdef CONFIG_VNC_TLS
+    }
+#endif
 
     memset (&secprops, 0, sizeof secprops);
     /* Inform SASL that we've got an external SSF layer from TLS */
diff --git a/ui/vnc-auth-vencrypt.c b/ui/vnc-auth-vencrypt.c
index a420ccb..55f24e4 100644
--- a/ui/vnc-auth-vencrypt.c
+++ b/ui/vnc-auth-vencrypt.c
@@ -63,23 +63,32 @@ 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(struct VncState *vs) {
     int ret;
 
     if ((ret = gnutls_handshake(vs->tls.session)) < 0) {
-       if (!gnutls_error_is_fatal(ret)) {
-           VNC_DEBUG("Handshake interrupted (blocking)\n");
-           if (!gnutls_record_get_direction(vs->tls.session))
-               qemu_set_fd_handler(vs->csock, vnc_tls_handshake_io, NULL, vs);
-           else
-               qemu_set_fd_handler(vs->csock, NULL, vnc_tls_handshake_io, vs);
-           return 0;
-       }
-       VNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret));
-       vnc_client_error(vs);
-       return -1;
+        if (!gnutls_error_is_fatal(ret)) {
+            GIOCondition condition;
+            VNC_DEBUG("Handshake interrupted (blocking)\n");
+            if (!gnutls_record_get_direction(vs->tls.session)) {
+                condition = G_IO_IN;
+            } else {
+                condition = G_IO_OUT;
+            }
+            if (vs->ioc_tag) {
+                g_source_remove(vs->ioc_tag);
+            }
+            vs->ioc_tag = qio_channel_add_watch(
+                vs->ioc, condition, vnc_tls_handshake_io, vs);
+            return 0;
+        }
+        VNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret));
+        vnc_client_error(vs);
+        return -1;
     }
 
     if (vs->vd->tls.x509verify) {
@@ -93,18 +102,26 @@ static int vnc_start_vencrypt_handshake(struct VncState *vs) {
     }
 
     VNC_DEBUG("Handshake done, switching to TLS data mode\n");
-    qemu_set_fd_handler2(vs->csock, NULL, 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);
 
     start_auth_vencrypt_subauth(vs);
 
     return 0;
 }
 
-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)
+{
     struct VncState *vs = (struct VncState *)opaque;
 
     VNC_DEBUG("Handshake IO continue\n");
     vnc_start_vencrypt_handshake(vs);
+    return TRUE;
 }
 
 
diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c
index 333c35e..3262ac6 100644
--- a/ui/vnc-jobs.c
+++ b/ui/vnc-jobs.c
@@ -168,7 +168,7 @@ void vnc_jobs_consume_buffer(VncState *vs)
         vnc_write(vs, vs->jobs_buffer.buffer, vs->jobs_buffer.offset);
         qio_buffer_reset(&vs->jobs_buffer);
     }
-    flush = vs->csock != -1 && vs->abort != true;
+    flush = vs->ioc != NULL && vs->abort != true;
     vnc_unlock_output(vs);
 
     if (flush) {
@@ -193,7 +193,8 @@ static void vnc_async_encoding_start(VncState *orig, VncState *local)
     local->hextile = orig->hextile;
     local->zrle = orig->zrle;
     local->output =  queue->buffer;
-    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 */
 
     qio_buffer_reset(&local->output);
 }
@@ -230,7 +231,7 @@ static int vnc_worker_thread_loop(VncJobQueue *queue)
     }
 
     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);
         goto disconnected;
     }
@@ -250,7 +251,7 @@ static int vnc_worker_thread_loop(VncJobQueue *queue)
     QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp) {
         int n;
 
-        if (job->vs->csock == -1) {
+        if (job->vs->ioc == NULL) {
             vnc_unlock_display(job->vs->vd);
             /* Copy persistent encoding data */
             vnc_async_encoding_end(job->vs, &vs);
@@ -272,8 +273,7 @@ static int vnc_worker_thread_loop(VncJobQueue *queue)
     vs.output.buffer[saved_offset + 1] = n_rectangles & 0xFF;
 
     vnc_lock_output(job->vs);
-
-    if (job->vs->csock != -1) {
+    if (job->vs->ioc != NULL) {
         qio_buffer_reserve(&job->vs->jobs_buffer, vs.output.offset);
         qio_buffer_append(&job->vs->jobs_buffer, vs.output.buffer,
                           vs.output.offset);
diff --git a/ui/vnc-tls.c b/ui/vnc-tls.c
index eddd39b..17e1c8f 100644
--- a/ui/vnc-tls.c
+++ b/ui/vnc-tls.c
@@ -69,13 +69,13 @@ static ssize_t vnc_tls_push(gnutls_transport_ptr_t transport,
                             const void *data,
                             size_t len) {
     struct VncState *vs = (struct VncState *)transport;
-    int ret;
-
- retry:
-    ret = send(vs->csock, data, len, 0);
+    ssize_t ret = qio_channel_write(vs->ioc, data, len, NULL);
     if (ret < 0) {
-        if (errno == EINTR)
-            goto retry;
+        if (ret == QIO_CHANNEL_ERR_BLOCK) {
+            errno = EAGAIN;
+        } else {
+            errno = EIO;
+        }
         return -1;
     }
     return ret;
@@ -86,13 +86,13 @@ static ssize_t vnc_tls_pull(gnutls_transport_ptr_t transport,
                             void *data,
                             size_t len) {
     struct VncState *vs = (struct VncState *)transport;
-    int ret;
-
- retry:
-    ret = qemu_recv(vs->csock, data, len, 0);
+    ssize_t ret = qio_channel_read(vs->ioc, data, len, NULL);
     if (ret < 0) {
-        if (errno == EINTR)
-            goto retry;
+        if (ret == QIO_CHANNEL_ERR_BLOCK) {
+            errno = EAGAIN;
+        } else {
+            errno = EIO;
+        }
         return -1;
     }
     return ret;
diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c
index d3ca97e..ad22694 100644
--- a/ui/vnc-ws.c
+++ b/ui/vnc-ws.c
@@ -25,20 +25,26 @@
 #ifdef CONFIG_VNC_TLS
 #include "qemu/sockets.h"
 
+static void vncws_handshake_read(VncState *vs);
+
 static int vncws_start_tls_handshake(struct VncState *vs)
 {
     int ret = gnutls_handshake(vs->tls.session);
 
     if (ret < 0) {
         if (!gnutls_error_is_fatal(ret)) {
+            GIOCondition condition;
             VNC_DEBUG("Handshake interrupted (blocking)\n");
             if (!gnutls_record_get_direction(vs->tls.session)) {
-                qemu_set_fd_handler(vs->csock, vncws_tls_handshake_io,
-                                    NULL, vs);
+                condition = G_IO_IN;
             } else {
-                qemu_set_fd_handler(vs->csock, NULL, vncws_tls_handshake_io,
-                                    vs);
+                condition = G_IO_OUT;
+            }
+            if (vs->ioc_tag) {
+                g_source_remove(vs->ioc_tag);
             }
+            vs->ioc_tag = qio_channel_add_watch(
+                vs->ioc, condition, vncws_tls_handshake_io, vs);
             return 0;
         }
         VNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret));
@@ -57,29 +63,35 @@ static int vncws_start_tls_handshake(struct VncState *vs)
     }
 
     VNC_DEBUG("Handshake done, switching to TLS data mode\n");
-    qemu_set_fd_handler2(vs->csock, NULL, 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);
 
     return 0;
 }
 
-void vncws_tls_handshake_io(void *opaque)
+gboolean vncws_tls_handshake_io(QIOChannel *ioc G_GNUC_UNUSED,
+                                GIOCondition condition G_GNUC_UNUSED,
+                                void *opaque)
 {
     struct VncState *vs = (struct VncState *)opaque;
 
     if (!vs->tls.session) {
         VNC_DEBUG("TLS Websocket setup\n");
         if (vnc_tls_client_setup(vs, vs->vd->tls.x509cert != NULL) < 0) {
-            return;
+            return TRUE;
         }
     }
     VNC_DEBUG("Handshake IO continue\n");
     vncws_start_tls_handshake(vs);
+    return TRUE;
 }
 #endif /* CONFIG_VNC_TLS */
 
-void vncws_handshake_read(void *opaque)
+static void vncws_handshake_read(VncState *vs)
 {
-    VncState *vs = opaque;
     uint8_t *handshake_end;
     long ret;
     /* Typical HTTP headers from novnc are 512 bytes, so limiting
@@ -89,7 +101,7 @@ void vncws_handshake_read(void *opaque)
     ret = vnc_client_read_buf(vs, qio_buffer_end(&vs->ws_input), want);
 
     if (!ret) {
-        if (vs->csock == -1) {
+        if (vs->disconnecting) {
             vnc_disconnect_finish(vs);
         }
         return;
@@ -99,7 +111,11 @@ void vncws_handshake_read(void *opaque)
     handshake_end = (uint8_t *)g_strstr_len((char *)vs->ws_input.buffer,
             vs->ws_input.offset, WS_HANDSHAKE_END);
     if (handshake_end) {
-        qemu_set_fd_handler2(vs->csock, NULL, 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);
         vncws_process_handshake(vs, vs->ws_input.buffer, vs->ws_input.offset);
         qio_buffer_advance(&vs->ws_input, handshake_end - vs->ws_input.buffer +
                 strlen(WS_HANDSHAKE_END));
@@ -110,6 +126,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)
 {
     int ret, err;
@@ -177,7 +202,11 @@ long vnc_client_write_ws(VncState *vs)
     qio_buffer_advance(&vs->ws_output, ret);
 
     if (vs->ws_output.offset == 0) {
-        qemu_set_fd_handler2(vs->csock, NULL, 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);
     }
 
     return ret;
diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h
index 5437102..2e79324 100644
--- a/ui/vnc-ws.h
+++ b/ui/vnc-ws.h
@@ -73,9 +73,13 @@ enum {
 };
 
 #ifdef CONFIG_VNC_TLS
-void vncws_tls_handshake_io(void *opaque);
+gboolean vncws_tls_handshake_io(QIOChannel *ioc,
+                                GIOCondition condition,
+                                void *opaque);
 #endif /* CONFIG_VNC_TLS */
-void vncws_handshake_read(void *opaque);
+gboolean vncws_handshake_io(QIOChannel *ioc,
+                            GIOCondition condition,
+                            void *opaque);
 long vnc_client_write_ws(VncState *vs);
 long vnc_client_read_ws(VncState *vs);
 void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size);
diff --git a/ui/vnc.c b/ui/vnc.c
index af1f792..6c08711 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -40,6 +40,8 @@
 #include "qapi-event.h"
 #include "crypto/hash.h"
 
+#include <glib/gi18n.h>
+
 #define VNC_REFRESH_INTERVAL_BASE GUI_REFRESH_INTERVAL_DEFAULT
 #define VNC_REFRESH_INTERVAL_INC  50
 #define VNC_REFRESH_INTERVAL_MAX  GUI_REFRESH_INTERVAL_IDLE
@@ -65,8 +67,8 @@ static void vnc_set_share_mode(VncState *vs, VncShareMode mode)
         [VNC_SHARE_MODE_EXCLUSIVE]    = "exclusive",
         [VNC_SHARE_MODE_DISCONNECTED] = "disconnected",
     };
-    fprintf(stderr, "%s/%d: %s -> %s\n", __func__,
-            vs->csock, mn[vs->share_mode], mn[mode]);
+    fprintf(stderr, "%s/%p: %s -> %s\n", __func__,
+            vs->ioc, mn[vs->share_mode], mn[mode]);
 #endif
 
     switch (vs->share_mode) {
@@ -100,105 +102,74 @@ 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,
-                           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;
+char *
+vnc_socket_local_addr(QIOChannelSocket *ioc,
+                      const char *format,
+                      Error **errp)
+{
+    char *host, *serv, *ret;
+    NetworkAddressFamily family;
 
-    salen = sizeof(sa);
-    if (getpeername(fd, (struct sockaddr*)&sa, &salen) < 0)
+    if (qio_channel_socket_get_local_addr_string(
+            ioc, &host, &serv, &family, errp) < 0) {
         return NULL;
+    }
 
-    return addr_to_string(format, &sa, salen);
+    ret = g_strdup_printf(format, host, serv);
+    g_free(host);
+    g_free(serv);
+    return ret;
 }
 
-static VncBasicInfo *vnc_basic_info_get(struct sockaddr_storage *sa,
-                                        socklen_t salen)
+char *
+vnc_socket_remote_addr(QIOChannelSocket *ioc,
+                       const char *format,
+                       Error **errp)
 {
-    VncBasicInfo *info;
-    char host[NI_MAXHOST];
-    char serv[NI_MAXSERV];
-    int err;
-
-    if ((err = getnameinfo((struct sockaddr *)sa, salen,
-                           host, sizeof(host),
-                           serv, sizeof(serv),
-                           NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
-        VNC_DEBUG("Cannot resolve address %d: %s\n",
-                  err, gai_strerror(err));
+    char *host, *serv, *ret;
+    NetworkAddressFamily family;
+
+    if (qio_channel_socket_get_remote_addr_string(
+            ioc, &host, &serv, &family, errp) < 0) {
         return NULL;
     }
 
-    info = g_malloc0(sizeof(VncBasicInfo));
-    info->host = g_strdup(host);
-    info->service = g_strdup(serv);
-    info->family = inet_netfamily(sa->ss_family);
-    return info;
+    ret = g_strdup_printf(format, host, serv);
+    g_free(host);
+    g_free(serv);
+    return ret;
 }
 
-static VncBasicInfo *vnc_basic_info_get_from_server_addr(int fd)
+
+static VncBasicInfo *
+vnc_basic_info_get_from_server_addr(QIOChannelSocket *ioc,
+                                    Error **errp)
 {
-    struct sockaddr_storage sa;
-    socklen_t salen;
+    VncBasicInfo *info = g_new0(VncBasicInfo, 1);
 
-    salen = sizeof(sa);
-    if (getsockname(fd, (struct sockaddr*)&sa, &salen) < 0) {
+    if (qio_channel_socket_get_local_addr_string(
+            ioc, &info->host, &info->service,
+            &info->family, errp) < 0) {
+        g_free(info);
         return NULL;
     }
-
-    return vnc_basic_info_get(&sa, salen);
+    return info;
 }
 
-static VncBasicInfo *vnc_basic_info_get_from_remote_addr(int fd)
+static VncBasicInfo *
+vnc_basic_info_get_from_remote_addr(QIOChannelSocket *ioc,
+                                    Error **errp)
 {
-    struct sockaddr_storage sa;
-    socklen_t salen;
+    VncBasicInfo *info = g_new0(VncBasicInfo, 1);
 
-    salen = sizeof(sa);
-    if (getpeername(fd, (struct sockaddr*)&sa, &salen) < 0) {
+    if (qio_channel_socket_get_remote_addr_string(
+            ioc, &info->host, &info->service,
+            &info->family, errp) < 0) {
+        g_free(info);
         return NULL;
     }
-
-    return vnc_basic_info_get(&sa, salen);
+    return info;
 }
 
 static const char *vnc_auth_name(VncDisplay *vd) {
@@ -255,8 +226,11 @@ static const char *vnc_auth_name(VncDisplay *vd) {
 static VncServerInfo *vnc_server_info_get(VncDisplay *vd)
 {
     VncServerInfo *info;
-    VncBasicInfo *bi = vnc_basic_info_get_from_server_addr(vd->lsock);
+    Error *err = NULL;
+    VncBasicInfo *bi = vnc_basic_info_get_from_server_addr(
+        vd->lsock, &err);
     if (!bi) {
+        error_free(err);
         return NULL;
     }
 
@@ -291,11 +265,15 @@ static void vnc_client_cache_auth(VncState *client)
 
 static void vnc_client_cache_addr(VncState *client)
 {
-    VncBasicInfo *bi = vnc_basic_info_get_from_remote_addr(client->csock);
+    Error *err = NULL;
+    VncBasicInfo *bi = vnc_basic_info_get_from_remote_addr(
+        client->sioc, &err);
 
     if (bi) {
         client->info = g_malloc0(sizeof(*client->info));
         client->info->base = bi;
+    } else {
+        error_free(err);
     }
 }
 
@@ -332,28 +310,19 @@ static void vnc_qmp_event(VncState *vs, QAPIEvent event)
 
 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;
+    VncBasicInfo *binfo;
+    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) {
+    binfo = vnc_basic_info_get_from_remote_addr(
+        client->sioc, &err);
+    if (!binfo) {
+        error_free(err);
         return NULL;
     }
 
     info = g_malloc0(sizeof(*info));
-    info->base = g_malloc0(sizeof(*info->base));
-    info->base->host = g_strdup(host);
-    info->base->service = g_strdup(serv);
-    info->base->family = inet_netfamily(sa.ss_family);
+    info->base = binfo;
     info->base->websocket = client->websocket;
 
 #ifdef CONFIG_VNC_TLS
@@ -409,43 +378,25 @@ VncInfo *qmp_query_vnc(Error **errp)
     if (vd == NULL || !vd->enabled) {
         info->enabled = false;
     } else {
-        struct sockaddr_storage sa;
-        socklen_t salen = sizeof(sa);
-        char host[NI_MAXHOST];
-        char serv[NI_MAXSERV];
-
         info->enabled = true;
 
         /* for compatibility with the original command */
         info->has_clients = true;
         info->clients = qmp_query_client_list(vd);
 
-        if (vd->lsock == -1) {
+        if (vd->lsock == NULL) {
             return info;
         }
 
-        if (getsockname(vd->lsock, (struct sockaddr *)&sa,
-                        &salen) == -1) {
-            error_set(errp, QERR_UNDEFINED_ERROR);
-            goto out_error;
-        }
-
-        if (getnameinfo((struct sockaddr *)&sa, salen,
-                        host, sizeof(host),
-                        serv, sizeof(serv),
-                        NI_NUMERICHOST | NI_NUMERICSERV) < 0) {
-            error_set(errp, QERR_UNDEFINED_ERROR);
+        if (qio_channel_socket_get_local_addr_string(
+                vd->lsock, &info->host, &info->service,
+                &info->family, errp) < 0) {
             goto out_error;
         }
 
         info->has_host = true;
-        info->host = g_strdup(host);
-
         info->has_service = true;
-        info->service = g_strdup(serv);
-
         info->has_family = true;
-        info->family = inet_netfamily(sa.ss_family);
 
         info->has_auth = true;
         info->auth = g_strdup(vnc_auth_name(vd));
@@ -458,28 +409,22 @@ out_error:
     return NULL;
 }
 
-static VncBasicInfoList *qmp_query_server_entry(int socket,
+static VncBasicInfoList *qmp_query_server_entry(QIOChannelSocket *ioc,
                                                 bool websocket,
                                                 VncBasicInfoList *prev)
 {
     VncBasicInfoList *list;
     VncBasicInfo *info;
-    struct sockaddr_storage sa;
-    socklen_t salen = sizeof(sa);
-    char host[NI_MAXHOST];
-    char serv[NI_MAXSERV];
-
-    if (getsockname(socket, (struct sockaddr *)&sa, &salen) < 0 ||
-        getnameinfo((struct sockaddr *)&sa, salen,
-                    host, sizeof(host), serv, sizeof(serv),
-                    NI_NUMERICHOST | NI_NUMERICSERV) < 0) {
-        return prev;
-    }
+    Error *err = NULL;
 
     info = g_new0(VncBasicInfo, 1);
-    info->host = g_strdup(host);
-    info->service = g_strdup(serv);
-    info->family = inet_netfamily(sa.ss_family);
+    if (qio_channel_socket_get_local_addr_string(
+            ioc, &info->host, &info->service,
+            &info->family, &err) < 0) {
+        error_free(err);
+        g_free(info);
+        return prev;
+    }
     info->websocket = websocket;
 
     list = g_new0(VncBasicInfoList, 1);
@@ -575,13 +520,13 @@ VncInfo2List *qmp_query_vnc_servers(Error **errp)
             info->has_display = true;
             info->display = g_strdup(dev->id);
         }
-        if (vd->lsock != -1) {
-            info->server = qmp_query_server_entry(vd->lsock, false,
-                                                  info->server);
+        if (vd->lsock != NULL) {
+            info->server = qmp_query_server_entry(
+                vd->lsock, false, info->server);
         }
-        if (vd->lwebsock != -1) {
-            info->server = qmp_query_server_entry(vd->lwebsock, true,
-                                                  info->server);
+        if (vd->lwebsock != NULL) {
+            info->server = qmp_query_server_entry(
+                vd->lwebsock, true, info->server);
         }
 
         item = g_new0(VncInfo2List, 1);
@@ -654,7 +599,7 @@ void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h,
 
 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;
     }
     if (vs->client_width == pixman_image_get_width(vs->vd->server) &&
@@ -1018,7 +963,7 @@ static int find_and_clear_dirty_height(struct VncState *vs,
 static int vnc_update_client(VncState *vs, int has_dirty, bool sync)
 {
     vs->has_dirty += has_dirty;
-    if (vs->need_update && vs->csock != -1) {
+    if (vs->need_update && vs->ioc != NULL) {
         VncDisplay *vd = vs->vd;
         VncJob *job;
         int y;
@@ -1082,7 +1027,7 @@ static int vnc_update_client(VncState *vs, int has_dirty, bool sync)
         return n;
     }
 
-    if (vs->csock == -1) {
+    if (vs->disconnecting) {
         vnc_disconnect_finish(vs);
     } else if (sync) {
         vnc_jobs_join(vs);
@@ -1164,12 +1109,15 @@ static void audio_del(VncState *vs)
 
 static void vnc_disconnect_start(VncState *vs)
 {
-    if (vs->csock == -1)
+    if (vs->disconnecting) {
         return;
+    }
     vnc_set_share_mode(vs, VNC_SHARE_MODE_DISCONNECTED);
-    qemu_set_fd_handler2(vs->csock, NULL, NULL, NULL, NULL);
-    closesocket(vs->csock);
-    vs->csock = -1;
+    if (vs->ioc_tag) {
+        g_source_remove(vs->ioc_tag);
+    }
+    qio_channel_close(vs->ioc, NULL);
+    vs->disconnecting = TRUE;
 }
 
 void vnc_disconnect_finish(VncState *vs)
@@ -1220,29 +1168,28 @@ void vnc_disconnect_finish(VncState *vs)
         g_free(vs->lossy_rect[i]);
     }
     g_free(vs->lossy_rect);
+
+    object_unref(OBJECT(vs->ioc));
+    vs->ioc = NULL;
+    object_unref(OBJECT(vs->sioc));
+    vs->sioc = NULL;
     g_free(vs);
 }
 
-int vnc_client_io_error(VncState *vs, int ret, int last_errno)
+int vnc_client_io_error(VncState *vs, int ret, Error **errp)
 {
-    if (ret == 0 || ret == -1) {
-        if (ret == -1) {
-            switch (last_errno) {
-                case EINTR:
-                case EAGAIN:
-#ifdef _WIN32
-                case WSAEWOULDBLOCK:
-#endif
-                    return 0;
-                default:
-                    break;
-            }
+    if (ret <= 0) {
+        if (ret == 0) {
+            VNC_DEBUG("Closing down client sock: EOF\n");
+        } else if (ret != QIO_CHANNEL_ERR_BLOCK) {
+            VNC_DEBUG("Closing down client sock: ret %d (%s)\n",
+                      ret, errp ? error_get_pretty(*errp) : "Unknown");
         }
-
-        VNC_DEBUG("Closing down client sock: ret %d, errno %d\n",
-                  ret, ret < 0 ? last_errno : 0);
         vnc_disconnect_start(vs);
-
+        if (errp) {
+            error_free(*errp);
+            *errp = NULL;
+        }
         return 0;
     }
     return ret;
@@ -1256,18 +1203,20 @@ void vnc_client_error(VncState *vs)
 }
 
 #ifdef CONFIG_VNC_TLS
-static long vnc_client_write_tls(gnutls_session_t *session,
-                                 const uint8_t *data,
-                                 size_t datalen)
+static ssize_t vnc_client_write_tls(gnutls_session_t *session,
+                                    const uint8_t *data,
+                                    size_t datalen,
+                                    Error **errp)
 {
-    long ret = gnutls_write(*session, data, datalen);
+    ssize_t ret = gnutls_write(*session, data, datalen);
     if (ret < 0) {
         if (ret == GNUTLS_E_AGAIN) {
-            errno = EAGAIN;
+            ret = -2;
         } else {
-            errno = EIO;
+            ret = -1;
+            error_setg_errno(errp, -EIO, "%s",
+                             _("Cannot write to TLS socket"));
         }
-        ret = -1;
     }
     return ret;
 }
@@ -1288,20 +1237,22 @@ static long vnc_client_write_tls(gnutls_session_t *session,
  * the requested 'datalen' if the socket would block. Returns
  * -1 on error, and disconnects the client socket.
  */
-long 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)
 {
-    long ret;
+    ssize_t ret;
+    Error *err = NULL;
 #ifdef CONFIG_VNC_TLS
     if (vs->tls.session) {
-        ret = vnc_client_write_tls(&vs->tls.session, data, datalen);
+        ret = vnc_client_write_tls(&vs->tls.session, data, datalen, &err);
     } else {
 #endif /* CONFIG_VNC_TLS */
-        ret = send(vs->csock, (const void *)data, datalen, 0);
+        ret = qio_channel_write(
+            vs->ioc, (const char *)data, datalen, &err);
 #ifdef CONFIG_VNC_TLS
     }
 #endif /* CONFIG_VNC_TLS */
     VNC_DEBUG("Wrote wire %p %zd -> %ld\n", data, datalen, ret);
-    return vnc_client_io_error(vs, ret, socket_error());
+    return vnc_client_io_error(vs, ret, &err);
 }
 
 
@@ -1315,9 +1266,9 @@ long vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
  * the buffered output data if the socket would block. Returns
  * -1 on error, and disconnects the client socket.
  */
-static long vnc_client_write_plain(VncState *vs)
+static ssize_t vnc_client_write_plain(VncState *vs)
 {
-    long ret;
+    ssize_t ret;
 
 #ifdef CONFIG_VNC_SASL
     VNC_DEBUG("Write Plain: Pending output %p size %zd offset %zd. Wait SSF %d\n",
@@ -1339,7 +1290,11 @@ static long vnc_client_write_plain(VncState *vs)
     qio_buffer_advance(&vs->output, ret);
 
     if (vs->output.offset == 0) {
-        qemu_set_fd_handler2(vs->csock, NULL, 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);
     }
 
     return ret;
@@ -1351,10 +1306,8 @@ static long vnc_client_write_plain(VncState *vs)
  * the client socket. Will delegate actual work according to whether
  * 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
     if (vs->sasl.conn &&
         vs->sasl.runSSF &&
@@ -1371,15 +1324,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);
     if (vs->output.offset || vs->ws_output.offset) {
-        vnc_client_write_locked(opaque);
-    } else if (vs->csock != -1) {
-        qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
+        vnc_client_write_locked(vs);
+    } else if (vs->ioc != NULL) {
+        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);
     }
     vnc_unlock_output(vs);
 }
@@ -1391,17 +1347,18 @@ void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting)
 }
 
 #ifdef CONFIG_VNC_TLS
-static long vnc_client_read_tls(gnutls_session_t *session, uint8_t *data,
-                                size_t datalen)
+static ssize_t vnc_client_read_tls(gnutls_session_t *session, uint8_t *data,
+                                   size_t datalen, Error **errp)
 {
-    long ret = gnutls_read(*session, data, datalen);
+    ssize_t ret = gnutls_read(*session, data, datalen);
     if (ret < 0) {
         if (ret == GNUTLS_E_AGAIN) {
-            errno = EAGAIN;
+            ret = QIO_CHANNEL_ERR_BLOCK;
         } else {
-            errno = EIO;
+            ret = -1;
+            error_setg_errno(errp, -EIO, "%s",
+                             _("Cannot read from TLS socket"));
         }
-        ret = -1;
     }
     return ret;
 }
@@ -1422,20 +1379,22 @@ static long vnc_client_read_tls(gnutls_session_t *session, uint8_t *data,
  * the requested 'datalen' if the socket would block. Returns
  * -1 on error, and disconnects the client socket.
  */
-long 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)
 {
-    long ret;
+    ssize_t ret;
+    Error *err = NULL;
 #ifdef CONFIG_VNC_TLS
     if (vs->tls.session) {
-        ret = vnc_client_read_tls(&vs->tls.session, data, datalen);
+        ret = vnc_client_read_tls(&vs->tls.session, data, datalen, &err);
     } else {
 #endif /* CONFIG_VNC_TLS */
-        ret = qemu_recv(vs->csock, data, datalen, 0);
+        ret = qio_channel_read(
+            vs->ioc, (char *)data, datalen, &err);
 #ifdef CONFIG_VNC_TLS
     }
 #endif /* CONFIG_VNC_TLS */
     VNC_DEBUG("Read wire %p %zd -> %ld\n", data, datalen, ret);
-    return vnc_client_io_error(vs, ret, socket_error());
+    return vnc_client_io_error(vs, ret, &err);
 }
 
 
@@ -1447,7 +1406,7 @@ long vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
  * Returns the number of bytes read. Returns -1 on error, and
  * disconnects the client socket.
  */
-static long vnc_client_read_plain(VncState *vs)
+static ssize_t vnc_client_read_plain(VncState *vs)
 {
     int ret;
     VNC_DEBUG("Read plain %p size %zd offset %zd\n",
@@ -1472,10 +1431,9 @@ static void vnc_jobs_bh(void *opaque)
  * the client socket. Will delegate actual work according to whether
  * 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;
-    long ret;
+    ssize_t ret;
 
 #ifdef CONFIG_VNC_SASL
     if (vs->sasl.conn && vs->sasl.runSSF)
@@ -1495,8 +1453,9 @@ void vnc_client_read(void *opaque)
             ret = vnc_client_read_plain(vs);
         }
     if (!ret) {
-        if (vs->csock == -1)
+        if (vs->disconnecting) {
             vnc_disconnect_finish(vs);
+        }
         return;
     }
 
@@ -1505,7 +1464,7 @@ void vnc_client_read(void *opaque)
         int ret;
 
         ret = vs->read_handler(vs, vs->input.buffer, len);
-        if (vs->csock == -1) {
+        if (vs->disconnecting) {
             vnc_disconnect_finish(vs);
             return;
         }
@@ -1518,12 +1477,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)
 {
     qio_buffer_reserve(&vs->output, len);
 
-    if (vs->csock != -1 && qio_buffer_empty(&vs->output)) {
-        qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, vnc_client_write, vs);
+    if (vs->ioc != NULL && qio_buffer_empty(&vs->output)) {
+        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);
     }
 
     qio_buffer_append(&vs->output, data, len);
@@ -1564,8 +1541,7 @@ void vnc_write_u8(VncState *vs, uint8_t value)
 void vnc_flush(VncState *vs)
 {
     vnc_lock_output(vs);
-    if (vs->csock != -1 && (vs->output.offset ||
-                            vs->ws_output.offset)) {
+    if (vs->ioc != NULL && (vs->output.offset || vs->ws_output.offset)) {
         vnc_client_write_locked(vs);
     }
     vnc_unlock_output(vs);
@@ -2946,13 +2922,16 @@ 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)
 {
     VncState *vs = g_malloc0(sizeof(VncState));
     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;
 
     if (skipauth) {
@@ -2975,23 +2954,24 @@ static void vnc_connect(VncDisplay *vd, int csock,
         vs->lossy_rect[i] = g_malloc0(VNC_STAT_COLS * sizeof (uint8_t));
     }
 
-    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);
-    qemu_set_nonblock(vs->csock);
+    qio_channel_set_blocking(vs->ioc, false);
     if (websocket) {
         vs->websocket = 1;
 #ifdef CONFIG_VNC_TLS
         if (vd->ws_tls) {
-            qemu_set_fd_handler2(vs->csock, NULL, vncws_tls_handshake_io,
-                                 NULL, vs);
+            vs->ioc_tag = qio_channel_add_watch(
+                vs->ioc, G_IO_IN, vncws_tls_handshake_io, vs);
         } else
 #endif /* CONFIG_VNC_TLS */
         {
-            qemu_set_fd_handler2(vs->csock, NULL, vncws_handshake_read,
-                                 NULL, vs);
+            vs->ioc_tag = qio_channel_add_watch(
+                vs->ioc, G_IO_IN, vncws_handshake_io, vs);
         }
     } else {
-        qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
+        vs->ioc_tag = qio_channel_add_watch(
+            vs->ioc, G_IO_IN, vnc_client_io, vs);
     }
 
     vnc_client_cache_addr(vs);
@@ -3045,35 +3025,28 @@ void vnc_init_state(VncState *vs)
     /* 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;
-    struct sockaddr_in addr;
-    socklen_t addrlen = sizeof(addr);
-    int csock;
+    QIOChannelSocket *sioc = NULL;
+    Error *err = NULL;
 
     /* Catch-up */
     graphic_hw_update(vs->dcl.con);
-    if (websocket) {
-        csock = qemu_accept(vs->lwebsock, (struct sockaddr *)&addr, &addrlen);
+    sioc = qio_channel_socket_accept(QIO_CHANNEL_SOCKET(ioc), &err);
+    if (sioc != NULL) {
+        qio_channel_socket_set_nodelay(sioc, true);
+        vnc_connect(vs, sioc, false,
+                    ioc != QIO_CHANNEL(vs->lsock));
+        object_unref(OBJECT(sioc));
     } else {
-        csock = qemu_accept(vs->lsock, (struct sockaddr *)&addr, &addrlen);
+        /* client probably closed connection before we got there */
+        error_free(err);
     }
 
-    if (csock != -1) {
-        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);
+    return TRUE;
 }
 
 static const DisplayChangeListenerOps dcl_ops = {
@@ -3099,9 +3072,6 @@ void vnc_display_init(const char *id)
     vs->id = strdup(id);
     QTAILQ_INSERT_TAIL(&vnc_displays, vs, next);
 
-    vs->lsock = -1;
-    vs->lwebsock = -1;
-
     QTAILQ_INIT(&vs->clients);
     vs->expires = TIME_MAX;
 
@@ -3129,16 +3099,20 @@ static void vnc_display_close(VncDisplay *vs)
         return;
     vs->enabled = false;
     vs->is_unix = false;
-    if (vs->lsock != -1) {
-        qemu_set_fd_handler2(vs->lsock, NULL, NULL, NULL, NULL);
-        close(vs->lsock);
-        vs->lsock = -1;
+    if (vs->lsock != NULL) {
+        if (vs->lsock_tag) {
+            g_source_remove(vs->lsock_tag);
+        }
+        object_unref(OBJECT(vs->lsock));
+        vs->lsock = NULL;
     }
     vs->ws_enabled = false;
-    if (vs->lwebsock != -1) {
-        qemu_set_fd_handler2(vs->lwebsock, NULL, NULL, NULL, NULL);
-        close(vs->lwebsock);
-        vs->lwebsock = -1;
+    if (vs->lwebsock != NULL) {
+        if (vs->lwebsock_tag) {
+            g_source_remove(vs->lwebsock_tag);
+        }
+        object_unref(OBJECT(vs->lwebsock));
+        vs->lwebsock = NULL;
     }
     vs->auth = VNC_AUTH_INVALID;
     vs->subauth = VNC_AUTH_INVALID;
@@ -3183,7 +3157,7 @@ char *vnc_display_local_addr(const char *id)
     VncDisplay *vs = vnc_display_find(id);
 
     assert(vs);
-    return vnc_socket_local_addr("%s:%s", vs->lsock);
+    return vnc_socket_local_addr(vs->lsock, "%s:%s", NULL);
 }
 
 static QemuOptsList qemu_vnc_opts = {
@@ -3408,6 +3382,7 @@ void vnc_display_open(const char *id, Error **errp)
     int acl = 0;
 #endif
     int lock_key_sync = 1;
+    Error *err = NULL;
 
     if (!vs) {
         error_setg(errp, "VNC display not active");
@@ -3613,8 +3588,9 @@ void vnc_display_open(const char *id, Error **errp)
     if (reverse) {
         /* connect to viewer */
         int csock;
-        vs->lsock = -1;
-        vs->lwebsock = -1;
+        QIOChannelSocket *sioc = NULL;
+        vs->lsock = NULL;
+        vs->lwebsock = NULL;
         if (strncmp(vnc, "unix:", 5) == 0) {
             csock = unix_connect(vnc+5, errp);
         } else {
@@ -3623,34 +3599,58 @@ void vnc_display_open(const char *id, Error **errp)
         if (csock < 0) {
             goto fail;
         }
-        vnc_connect(vs, csock, false, false);
+        sioc = qio_channel_socket_new_fd(csock, &err);
+        if (!sioc) {
+            close(csock);
+            goto fail;
+        }
+        vnc_connect(vs, sioc, false, false);
+        object_unref(OBJECT(sioc));
     } else {
         /* listen for connects */
+        char *dpy;
+        int lsock;
+        dpy = g_malloc(256);
         if (strncmp(vnc, "unix:", 5) == 0) {
-            vs->lsock = unix_listen(vnc+5, NULL, 0, errp);
+            lsock = unix_listen(vnc+5, NULL, 0, errp);
             vs->is_unix = true;
         } else {
-            vs->lsock = inet_listen_opts(sopts, 5900, errp);
-            if (vs->lsock < 0) {
+            lsock = inet_listen_opts(sopts, 5900, errp);
+            if (lsock < 0) {
+                g_free(dpy);
                 goto fail;
             }
             if (vs->ws_enabled) {
-                vs->lwebsock = inet_listen_opts(wsopts, 0, errp);
-                if (vs->lwebsock < 0) {
-                    if (vs->lsock != -1) {
-                        close(vs->lsock);
-                        vs->lsock = -1;
-                    }
+                int lwebsock;
+                lwebsock = inet_listen_opts(wsopts, 0, errp);
+                if (lwebsock < 0) {
+                    close(lsock);
+                    lsock = -1;
                     goto fail;
                 }
+                vs->lwebsock = qio_channel_socket_new_fd(lwebsock, &err);
+                if (!vs->lwebsock) {
+                    close(lsock);
+                    close(lwebsock);
+                    lwebsock = lsock = -1;
+                }
             }
         }
         vs->enabled = true;
-        qemu_set_fd_handler2(vs->lsock, NULL,
-                vnc_listen_regular_read, NULL, vs);
+        vs->lsock = qio_channel_socket_new_fd(lsock, &err);
+        if (!vs->lsock) {
+            object_unref(OBJECT(vs->lwebsock));
+            vs->lwebsock = NULL;
+            close(lsock);
+            goto fail;
+        }
+        vs->lsock_tag = qio_channel_add_watch(
+            QIO_CHANNEL(vs->lsock),
+            G_IO_IN, vnc_listen_io, vs);
         if (vs->ws_enabled) {
-            qemu_set_fd_handler2(vs->lwebsock, NULL,
-                    vnc_listen_websocket_read, NULL, vs);
+            vs->lwebsock_tag = qio_channel_add_watch(
+                QIO_CHANNEL(vs->lwebsock),
+                G_IO_IN, vnc_listen_io, vs);
         }
     }
     qemu_opts_del(sopts);
@@ -3662,16 +3662,27 @@ fail:
     qemu_opts_del(wsopts);
     vs->enabled = false;
     vs->ws_enabled = false;
+    if (err) {
+        fprintf(stderr, "Unable to initialize VNC: %s\n",
+                error_get_pretty(err));
+        error_free(err);
+    }
 }
 
 void vnc_display_add_client(const char *id, int csock, bool skipauth)
 {
     VncDisplay *vs = vnc_display_find(id);
+    QIOChannelSocket *sioc;
 
     if (!vs) {
         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)
diff --git a/ui/vnc.h b/ui/vnc.h
index f44a949..593e6b6 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -34,6 +34,7 @@
 #include "audio/audio.h"
 #include "qemu/bitmap.h"
 #include "io/buffer.h"
+#include "io/channel-socket.h"
 #include <zlib.h>
 #include <stdbool.h>
 
@@ -147,8 +148,10 @@ struct VncDisplay
     int num_exclusive;
     int connections_limit;
     VncSharePolicy share_policy;
-    int lsock;
-    int lwebsock;
+    QIOChannelSocket *lsock;
+    guint lsock_tag;
+    QIOChannelSocket *lwebsock;
+    guint lwebsock_tag;
     bool ws_enabled;
     DisplaySurface *ds;
     DisplayChangeListener dcl;
@@ -251,7 +254,10 @@ struct VncJob
 
 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);
     uint8_t **lossy_rect; /* Not an Array to avoid costly memcpy in
@@ -504,11 +510,12 @@ enum {
  *****************************************************************************/
 
 /* Event loop functions */
-void vnc_client_read(void *opaque);
-void vnc_client_write(void *opaque);
+gboolean vnc_client_io(QIOChannel *ioc,
+                       GIOCondition condition,
+                       void *opaque);
 
-long vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen);
-long vnc_client_write_buf(VncState *vs, const 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);
 
 /* Protocol I/O functions */
 void vnc_write(VncState *vs, const void *data, size_t len);
@@ -527,7 +534,7 @@ uint32_t read_u32(uint8_t *data, size_t offset);
 
 /* Protocol stage functions */
 void vnc_client_error(VncState *vs);
-int vnc_client_io_error(VncState *vs, int ret, int last_errno);
+int vnc_client_io_error(VncState *vs, int ret, Error **errp);
 
 void start_client_init(VncState *vs);
 void start_auth_vnc(VncState *vs);
@@ -535,8 +542,14 @@ void start_auth_vnc(VncState *vs);
 
 /* Misc helpers */
 
-char *vnc_socket_local_addr(const char *format, int fd);
-char *vnc_socket_remote_addr(const char *format, int fd);
+char *
+vnc_socket_local_addr(QIOChannelSocket *ioc,
+                      const char *format,
+                      Error **errp);
+char *
+vnc_socket_remote_addr(QIOChannelSocket *ioc,
+                       const char *format,
+                       Error **errp);
 
 static inline uint32_t vnc_has_feature(VncState *vs, int feature) {
     return (vs->features & (1 << feature));
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 30/34] ui: convert VNC server to use QIOChannelTLS
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (28 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 29/34] ui: convert VNC server to use QEMUIOChannelSocket classes Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 31/34] ui: convert VNC server to use QIOChannelWebsock Daniel P. Berrange
                   ` (4 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

Switch VNC server over to using the QIOChannelTLS object for
the TLS session. This removes all the VNC specific code for
dealing with TLS. This has the nice effect that all the
CONFIG_VNC_TLS conditionals go away and the user gets an
actual error message when requesting TLS instead of it being
silently ignored.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 configure              |  31 ------
 qemu-options.hx        |  28 ++++-
 ui/Makefile.objs       |   2 +-
 ui/vnc-auth-sasl.c     |  46 +++-----
 ui/vnc-auth-vencrypt.c | 107 ++++++------------
 ui/vnc-tls.h           |  69 ------------
 ui/vnc-ws.c            |  89 +++++++--------
 ui/vnc-ws.h            |   2 -
 ui/vnc.c               | 296 ++++++++++++++++++++++++++-----------------------
 ui/vnc.h               |  13 +--
 10 files changed, 279 insertions(+), 404 deletions(-)
 delete mode 100644 ui/vnc-tls.h

diff --git a/configure b/configure
index fa07f2c..24133c0 100755
--- a/configure
+++ b/configure
@@ -241,7 +241,6 @@ vnc="yes"
 sparse="no"
 uuid=""
 vde=""
-vnc_tls=""
 vnc_sasl=""
 vnc_jpeg=""
 vnc_png=""
@@ -872,10 +871,6 @@ for opt do
   ;;
   --disable-strip) strip_opt="no"
   ;;
-  --disable-vnc-tls) vnc_tls="no"
-  ;;
-  --enable-vnc-tls) vnc_tls="yes"
-  ;;
   --disable-vnc-sasl) vnc_sasl="no"
   ;;
   --enable-vnc-sasl) vnc_sasl="yes"
@@ -2369,28 +2364,6 @@ EOF
   fi
 fi
 
-##########################################
-# VNC TLS/WS detection
-if test "$vnc" = "yes" -a "$vnc_tls" != "no" ; then
-  cat > $TMPC <<EOF
-#include <gnutls/gnutls.h>
-int main(void) { gnutls_session_t s; gnutls_init(&s, GNUTLS_SERVER); return 0; }
-EOF
-  vnc_tls_cflags=`$pkg_config --cflags gnutls 2> /dev/null`
-  vnc_tls_libs=`$pkg_config --libs gnutls 2> /dev/null`
-  if compile_prog "$vnc_tls_cflags" "$vnc_tls_libs" ; then
-    if test "$vnc_tls" != "no" ; then
-      vnc_tls=yes
-    fi
-    libs_softmmu="$vnc_tls_libs $libs_softmmu"
-    QEMU_CFLAGS="$QEMU_CFLAGS $vnc_tls_cflags"
-  else
-    if test "$vnc_tls" = "yes" ; then
-      feature_not_found "vnc-tls" "Install gnutls devel"
-    fi
-    vnc_tls=no
-  fi
-fi
 
 ##########################################
 # VNC SASL detection
@@ -4432,7 +4405,6 @@ echo "Block whitelist (ro) $block_drv_ro_whitelist"
 echo "VirtFS support    $virtfs"
 echo "VNC support       $vnc"
 if test "$vnc" = "yes" ; then
-    echo "VNC TLS support   $vnc_tls"
     echo "VNC SASL support  $vnc_sasl"
     echo "VNC JPEG support  $vnc_jpeg"
     echo "VNC PNG support   $vnc_png"
@@ -4628,9 +4600,6 @@ echo "CONFIG_BDRV_RO_WHITELIST=$block_drv_ro_whitelist" >> $config_host_mak
 if test "$vnc" = "yes" ; then
   echo "CONFIG_VNC=y" >> $config_host_mak
 fi
-if test "$vnc_tls" = "yes" ; then
-  echo "CONFIG_VNC_TLS=y" >> $config_host_mak
-fi
 if test "$vnc_sasl" = "yes" ; then
   echo "CONFIG_VNC_SASL=y" >> $config_host_mak
 fi
diff --git a/qemu-options.hx b/qemu-options.hx
index 1c88253..44d9be2 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1206,8 +1206,9 @@ By definition the Websocket port is 5700+@var{display}. If @var{host} is
 specified connections will only be allowed from this host.
 As an alternative the Websocket port could be specified by using
 @code{websocket}=@var{port}.
-TLS encryption for the Websocket connection is supported if the required
-certificates are specified with the VNC option @option{x509}.
+If no TLS credentials are provided, the websocket connection runs in
+uncrypted mode. If TLS credentials are provided, the websocket connection
+requires encrypted client connections.
 
 @item password
 
@@ -1228,6 +1229,20 @@ date and time).
 You can also use keywords "now" or "never" for the expiration time to
 allow <protocol> password to expire immediately or never expire.
 
+@item tls-cred=@var{ID}
+
+Provides the ID of a set of TLS credentials to use to secure the
+VNC server. They will apply to both the normal VNC server socket
+and the websocket socket (if enabled). Setting TLS credentials
+will cause the VNC server socket to enable the VeNCrypt auth
+mechanism.  The credentials should have been previously created
+using the @option{-object qcrypto-tls-cred} argument.
+
+The @option{tls-cred} parameter obsoletes the @option{tls},
+@option{x509}, and @option{x509verify} options, and as such
+it is not permitted to set both new and old type options at
+the same time.
+
 @item tls
 
 Require that client use TLS when communicating with the VNC server. This
@@ -1235,6 +1250,9 @@ uses anonymous TLS credentials so is susceptible to a man-in-the-middle
 attack. It is recommended that this option be combined with either the
 @option{x509} or @option{x509verify} options.
 
+This option is now deprecated in favour of using the @option{tls-cred}
+argument.
+
 @item x509=@var{/path/to/certificate/dir}
 
 Valid if @option{tls} is specified. Require that x509 credentials are used
@@ -1244,6 +1262,9 @@ to provide authentication of the client when this is used. The path following
 this option specifies where the x509 certificates are to be loaded from.
 See the @ref{vnc_security} section for details on generating certificates.
 
+This option is now deprecated in favour of using the @option{tls-cred}
+argument.
+
 @item x509verify=@var{/path/to/certificate/dir}
 
 Valid if @option{tls} is specified. Require that x509 credentials are used
@@ -1257,6 +1278,9 @@ path following this option specifies where the x509 certificates are to
 be loaded from. See the @ref{vnc_security} section for details on generating
 certificates.
 
+This option is now deprecated in favour of using the @option{tls-cred}
+argument.
+
 @item sasl
 
 Require that the client use SASL to authenticate with the VNC server.
diff --git a/ui/Makefile.objs b/ui/Makefile.objs
index 6458d3c..b8314f5 100644
--- a/ui/Makefile.objs
+++ b/ui/Makefile.objs
@@ -2,7 +2,7 @@ vnc-obj-y += vnc.o
 vnc-obj-y += vnc-enc-zlib.o vnc-enc-hextile.o
 vnc-obj-y += vnc-enc-tight.o vnc-palette.o
 vnc-obj-y += vnc-enc-zrle.o
-vnc-obj-$(CONFIG_VNC_TLS) += vnc-tls.o vnc-auth-vencrypt.o
+vnc-obj-y += vnc-auth-vencrypt.o
 vnc-obj-$(CONFIG_VNC_SASL) += vnc-auth-sasl.o
 vnc-obj-y += vnc-ws.o
 vnc-obj-y += vnc-jobs.o
diff --git a/ui/vnc-auth-sasl.c b/ui/vnc-auth-sasl.c
index 7851037..4d05a65 100644
--- a/ui/vnc-auth-sasl.c
+++ b/ui/vnc-auth-sasl.c
@@ -495,7 +495,7 @@ void start_auth_sasl(VncState *vs)
 {
     const char *mechlist = NULL;
     sasl_security_properties_t secprops;
-    int err;
+    int ret;
     char *localAddr, *remoteAddr;
     int mechlistlen;
 
@@ -513,7 +513,7 @@ void start_auth_sasl(VncState *vs)
         goto authabort;
     }
 
-    err = sasl_server_new("vnc",
+    ret = sasl_server_new("vnc",
                           NULL, /* FQDN - just delegates to gethostname */
                           NULL, /* User realm */
                           localAddr,
@@ -525,54 +525,44 @@ void start_auth_sasl(VncState *vs)
     g_free(remoteAddr);
     localAddr = remoteAddr = NULL;
 
-    if (err != SASL_OK) {
+    if (ret != SASL_OK) {
         VNC_DEBUG("sasl context setup failed %d (%s)",
-                  err, sasl_errstring(err, NULL, NULL));
+                  ret, sasl_errstring(ret, NULL, NULL));
         vs->sasl.conn = NULL;
         goto authabort;
     }
 
-#ifdef CONFIG_VNC_TLS
     /* Inform SASL that we've got an external SSF layer from TLS/x509 */
     if (vs->auth == VNC_AUTH_VENCRYPT &&
         vs->subauth == VNC_AUTH_VENCRYPT_X509SASL) {
-        gnutls_cipher_algorithm_t cipher;
-        sasl_ssf_t ssf;
-
-        cipher = gnutls_cipher_get(vs->tls.session);
-        ssf = (sasl_ssf_t)gnutls_cipher_get_key_size(cipher);
-        if (!ssf) {
-            VNC_DEBUG("%s", "cannot TLS get cipher size\n");
-            sasl_dispose(&vs->sasl.conn);
-            vs->sasl.conn = NULL;
+        Error *err = NULL;
+        int ssf = qcrypto_tls_session_get_key_size(vs->tls, &err);
+        if (ssf < 0) {
+            VNC_DEBUG("Cannot get key size %s\n", error_get_pretty(err));
+            error_free(err);
             goto authabort;
         }
         ssf *= 8; /* tls key size is bytes, sasl wants bits */
 
-        err = sasl_setprop(vs->sasl.conn, SASL_SSF_EXTERNAL, &ssf);
-        if (err != SASL_OK) {
+        ret = sasl_setprop(vs->sasl.conn, SASL_SSF_EXTERNAL, &ssf);
+        if (ret != SASL_OK) {
             VNC_DEBUG("cannot set SASL external SSF %d (%s)\n",
-                      err, sasl_errstring(err, NULL, NULL));
+                      ret, sasl_errstring(ret, NULL, NULL));
             sasl_dispose(&vs->sasl.conn);
             vs->sasl.conn = NULL;
             goto authabort;
         }
     } else {
-#endif /* CONFIG_VNC_TLS */
         vs->sasl.wantSSF = 1;
-#ifdef CONFIG_VNC_TLS
     }
-#endif
 
     memset (&secprops, 0, sizeof secprops);
     /* Inform SASL that we've got an external SSF layer from TLS */
     if (vs->vd->is_unix
-#ifdef CONFIG_VNC_TLS
         /* Disable SSF, if using TLS+x509+SASL only. TLS without x509
            is not sufficiently strong */
         || (vs->auth == VNC_AUTH_VENCRYPT &&
             vs->subauth == VNC_AUTH_VENCRYPT_X509SASL)
-#endif /* CONFIG_VNC_TLS */
         ) {
         /* If we've got TLS or UNIX domain sock, we don't care about SSF */
         secprops.min_ssf = 0;
@@ -589,16 +579,16 @@ void start_auth_sasl(VncState *vs)
             SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT;
     }
 
-    err = sasl_setprop(vs->sasl.conn, SASL_SEC_PROPS, &secprops);
-    if (err != SASL_OK) {
+    ret = sasl_setprop(vs->sasl.conn, SASL_SEC_PROPS, &secprops);
+    if (ret != SASL_OK) {
         VNC_DEBUG("cannot set SASL security props %d (%s)\n",
-                  err, sasl_errstring(err, NULL, NULL));
+                  ret, sasl_errstring(ret, NULL, NULL));
         sasl_dispose(&vs->sasl.conn);
         vs->sasl.conn = NULL;
         goto authabort;
     }
 
-    err = sasl_listmech(vs->sasl.conn,
+    ret = sasl_listmech(vs->sasl.conn,
                         NULL, /* Don't need to set user */
                         "", /* Prefix */
                         ",", /* Separator */
@@ -606,9 +596,9 @@ void start_auth_sasl(VncState *vs)
                         &mechlist,
                         NULL,
                         NULL);
-    if (err != SASL_OK) {
+    if (ret != SASL_OK) {
         VNC_DEBUG("cannot list SASL mechanisms %d (%s)\n",
-                  err, sasl_errdetail(vs->sasl.conn));
+                  ret, sasl_errdetail(vs->sasl.conn));
         sasl_dispose(&vs->sasl.conn);
         vs->sasl.conn = NULL;
         goto authabort;
diff --git a/ui/vnc-auth-vencrypt.c b/ui/vnc-auth-vencrypt.c
index 55f24e4..99846fa 100644
--- a/ui/vnc-auth-vencrypt.c
+++ b/ui/vnc-auth-vencrypt.c
@@ -63,76 +63,22 @@ static void start_auth_vencrypt_subauth(VncState *vs)
     }
 }
 
-static gboolean vnc_tls_handshake_io(QIOChannel *ioc,
-                                     GIOCondition condition,
-                                     void *opaque);
-
-static int vnc_start_vencrypt_handshake(struct VncState *vs) {
-    int ret;
-
-    if ((ret = gnutls_handshake(vs->tls.session)) < 0) {
-        if (!gnutls_error_is_fatal(ret)) {
-            GIOCondition condition;
-            VNC_DEBUG("Handshake interrupted (blocking)\n");
-            if (!gnutls_record_get_direction(vs->tls.session)) {
-                condition = G_IO_IN;
-            } else {
-                condition = G_IO_OUT;
-            }
-            if (vs->ioc_tag) {
-                g_source_remove(vs->ioc_tag);
-            }
-            vs->ioc_tag = qio_channel_add_watch(
-                vs->ioc, condition, vnc_tls_handshake_io, vs);
-            return 0;
-        }
-        VNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret));
-        vnc_client_error(vs);
-        return -1;
-    }
-
-    if (vs->vd->tls.x509verify) {
-        if (vnc_tls_validate_certificate(vs) < 0) {
-            VNC_DEBUG("Client verification failed\n");
-            vnc_client_error(vs);
-            return -1;
-        } else {
-            VNC_DEBUG("Client verification passed\n");
-        }
-    }
-
-    VNC_DEBUG("Handshake done, switching to TLS data mode\n");
-    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);
-
-    start_auth_vencrypt_subauth(vs);
-
-    return 0;
-}
-
-static gboolean vnc_tls_handshake_io(QIOChannel *ioc G_GNUC_UNUSED,
-                                     GIOCondition condition G_GNUC_UNUSED,
-                                     void *opaque)
+static void vnc_tls_handshake_done(QIOTask *task,
+                                   gpointer user_data)
 {
-    struct VncState *vs = (struct VncState *)opaque;
+    struct VncState *vs = user_data;
 
-    VNC_DEBUG("Handshake IO continue\n");
-    vnc_start_vencrypt_handshake(vs);
-    return TRUE;
+    if (task->err) {
+        VNC_DEBUG("Handshake failed %s\n",
+                  error_get_pretty(task->err));
+        vnc_client_error(vs);
+    } else {
+        vs->ioc_tag = qio_channel_add_watch(
+            vs->ioc, G_IO_IN | G_IO_OUT, vnc_client_io, vs);
+        start_auth_vencrypt_subauth(vs);
+    }
 }
 
-
-
-#define NEED_X509_AUTH(vs)                              \
-    ((vs)->subauth == VNC_AUTH_VENCRYPT_X509NONE ||   \
-     (vs)->subauth == VNC_AUTH_VENCRYPT_X509VNC ||    \
-     (vs)->subauth == VNC_AUTH_VENCRYPT_X509PLAIN ||  \
-     (vs)->subauth == VNC_AUTH_VENCRYPT_X509SASL)
-
-
 static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len)
 {
     int auth = read_u32(data, 0);
@@ -143,20 +89,37 @@ static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len
         vnc_flush(vs);
         vnc_client_error(vs);
     } else {
+        Error *err = NULL;
+        QIOChannelTLS *tls;
         VNC_DEBUG("Accepting auth %d, setting up TLS for handshake\n", auth);
         vnc_write_u8(vs, 1); /* Accept auth */
         vnc_flush(vs);
 
-        if (vnc_tls_client_setup(vs, NEED_X509_AUTH(vs)) < 0) {
-            VNC_DEBUG("Failed to setup TLS\n");
-            return 0;
+        if (vs->ioc_tag) {
+            g_source_remove(vs->ioc_tag);
+            vs->ioc_tag = 0;
         }
 
-        VNC_DEBUG("Start TLS VeNCrypt handshake process\n");
-        if (vnc_start_vencrypt_handshake(vs) < 0) {
-            VNC_DEBUG("Failed to start TLS handshake\n");
+        tls = qio_channel_tls_new_server(
+            vs->ioc,
+            vs->vd->creds,
+            vs->vd->tlsaclname,
+            &err);
+        if (!tls) {
+            VNC_DEBUG("Failed to setup TLS %s\n", error_get_pretty(err));
+            error_free(err);
+            vnc_client_error(vs);
             return 0;
         }
+
+        object_unref(OBJECT(vs->ioc));
+        vs->ioc = QIO_CHANNEL(tls);
+        vs->tls = qio_channel_tls_get_session(tls);
+
+        qio_channel_tls_handshake(tls,
+                                  vnc_tls_handshake_done,
+                                  vs,
+                                  NULL);
     }
     return 0;
 }
diff --git a/ui/vnc-tls.h b/ui/vnc-tls.h
deleted file mode 100644
index f9829c7..0000000
--- a/ui/vnc-tls.h
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * QEMU VNC display driver. TLS helpers
- *
- * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
- * Copyright (C) 2006 Fabrice Bellard
- * 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_TLS_H__
-#define __QEMU_VNC_TLS_H__
-
-#include <gnutls/gnutls.h>
-#include <gnutls/x509.h>
-
-#include "qemu/acl.h"
-
-typedef struct VncDisplayTLS VncDisplayTLS;
-typedef struct VncStateTLS VncStateTLS;
-
-/* Server state */
-struct VncDisplayTLS {
-    int x509verify; /* Non-zero if server requests & validates client cert */
-    qemu_acl *acl;
-
-    /* Paths to x509 certs/keys */
-    char *x509cacert;
-    char *x509cacrl;
-    char *x509cert;
-    char *x509key;
-};
-
-/* Per client state */
-struct VncStateTLS {
-    gnutls_session_t session;
-
-    /* Client's Distinguished Name from the x509 cert */
-    char *dname;
-};
-
-int vnc_tls_client_setup(VncState *vs, int x509Creds);
-void vnc_tls_client_cleanup(VncState *vs);
-
-int vnc_tls_validate_certificate(VncState *vs);
-
-int vnc_tls_set_x509_creds_dir(VncDisplay *vd,
-			       const char *path);
-
-
-#endif /* __QEMU_VNC_TLS_H__ */
-
diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c
index ad22694..714e513 100644
--- a/ui/vnc-ws.c
+++ b/ui/vnc-ws.c
@@ -22,73 +22,62 @@
 #include "qemu/main-loop.h"
 #include "crypto/hash.h"
 
-#ifdef CONFIG_VNC_TLS
 #include "qemu/sockets.h"
 
 static void vncws_handshake_read(VncState *vs);
 
-static int vncws_start_tls_handshake(struct VncState *vs)
+static void vncws_tls_handshake_done(QIOTask *task,
+                                     gpointer user_data)
 {
-    int ret = gnutls_handshake(vs->tls.session);
-
-    if (ret < 0) {
-        if (!gnutls_error_is_fatal(ret)) {
-            GIOCondition condition;
-            VNC_DEBUG("Handshake interrupted (blocking)\n");
-            if (!gnutls_record_get_direction(vs->tls.session)) {
-                condition = G_IO_IN;
-            } else {
-                condition = G_IO_OUT;
-            }
-            if (vs->ioc_tag) {
-                g_source_remove(vs->ioc_tag);
-            }
-            vs->ioc_tag = qio_channel_add_watch(
-                vs->ioc, condition, vncws_tls_handshake_io, vs);
-            return 0;
-        }
-        VNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret));
-        vnc_client_error(vs);
-        return -1;
-    }
-
-    if (vs->vd->tls.x509verify) {
-        if (vnc_tls_validate_certificate(vs) < 0) {
-            VNC_DEBUG("Client verification failed\n");
-            vnc_client_error(vs);
-            return -1;
-        } else {
-            VNC_DEBUG("Client verification passed\n");
-        }
-    }
+    struct VncState *vs = user_data;
 
-    VNC_DEBUG("Handshake done, switching to TLS data mode\n");
-    if (vs->ioc_tag) {
-        g_source_remove(vs->ioc_tag);
+    if (task->err) {
+        VNC_DEBUG("Handshake failed %s\n", error_get_pretty(task->err));
+        vnc_client_error(vs);
+    } else {
+        vs->ioc_tag = qio_channel_add_watch(
+            QIO_CHANNEL(vs->ioc), G_IO_IN, vncws_handshake_io, vs);
     }
-    vs->ioc_tag = qio_channel_add_watch(
-        vs->ioc, G_IO_IN, vncws_handshake_io, vs);
-
-    return 0;
 }
 
+
 gboolean vncws_tls_handshake_io(QIOChannel *ioc G_GNUC_UNUSED,
                                 GIOCondition condition G_GNUC_UNUSED,
                                 void *opaque)
 {
-    struct VncState *vs = (struct VncState *)opaque;
+    VncState *vs = opaque;
+    QIOChannelTLS *tls;
+    Error *err = NULL;
 
-    if (!vs->tls.session) {
-        VNC_DEBUG("TLS Websocket setup\n");
-        if (vnc_tls_client_setup(vs, vs->vd->tls.x509cert != NULL) < 0) {
-            return TRUE;
-        }
+    VNC_DEBUG("TLS Websocket connection required\n");
+    if (vs->ioc_tag) {
+        g_source_remove(vs->ioc_tag);
+        vs->ioc_tag = 0;
     }
-    VNC_DEBUG("Handshake IO continue\n");
-    vncws_start_tls_handshake(vs);
+
+    tls = qio_channel_tls_new_server(
+        vs->ioc,
+        vs->vd->creds,
+        vs->vd->tlsaclname,
+        &err);
+    if (!tls) {
+        VNC_DEBUG("Failed to setup TLS %s\n", error_get_pretty(err));
+        error_free(err);
+        vnc_client_error(vs);
+        return TRUE;
+    }
+
+    object_unref(OBJECT(vs->ioc));
+    vs->ioc = QIO_CHANNEL(tls);
+    vs->tls = qio_channel_tls_get_session(tls);
+
+    qio_channel_tls_handshake(tls,
+                              vncws_tls_handshake_done,
+                              vs,
+                              NULL);
+
     return TRUE;
 }
-#endif /* CONFIG_VNC_TLS */
 
 static void vncws_handshake_read(VncState *vs)
 {
diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h
index 2e79324..21caaf3 100644
--- a/ui/vnc-ws.h
+++ b/ui/vnc-ws.h
@@ -72,11 +72,9 @@ enum {
     WS_OPCODE_PONG = 0xA
 };
 
-#ifdef CONFIG_VNC_TLS
 gboolean vncws_tls_handshake_io(QIOChannel *ioc,
                                 GIOCondition condition,
                                 void *opaque);
-#endif /* CONFIG_VNC_TLS */
 gboolean vncws_handshake_io(QIOChannel *ioc,
                             GIOCondition condition,
                             void *opaque);
diff --git a/ui/vnc.c b/ui/vnc.c
index 6c08711..65d7493 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -39,6 +39,7 @@
 #include "ui/input.h"
 #include "qapi-event.h"
 #include "crypto/hash.h"
+#include "qom/object_interfaces.h"
 
 #include <glib/gi18n.h>
 
@@ -191,7 +192,6 @@ static const char *vnc_auth_name(VncDisplay *vd) {
     case VNC_AUTH_TLS:
         return "tls";
     case VNC_AUTH_VENCRYPT:
-#ifdef CONFIG_VNC_TLS
         switch (vd->subauth) {
         case VNC_AUTH_VENCRYPT_PLAIN:
             return "vencrypt+plain";
@@ -214,9 +214,6 @@ static const char *vnc_auth_name(VncDisplay *vd) {
         default:
             return "vencrypt";
         }
-#else
-        return "vencrypt";
-#endif
     case VNC_AUTH_SASL:
         return "sasl";
     }
@@ -247,13 +244,12 @@ static void vnc_client_cache_auth(VncState *client)
         return;
     }
 
-#ifdef CONFIG_VNC_TLS
-    if (client->tls.session &&
-        client->tls.dname) {
-        client->info->has_x509_dname = true;
-        client->info->x509_dname = g_strdup(client->tls.dname);
+    if (client->tls) {
+        client->info->x509_dname =
+            qcrypto_tls_session_get_peer_name(client->tls);
+        client->info->has_x509_dname =
+            client->info->x509_dname != NULL;
     }
-#endif
 #ifdef CONFIG_VNC_SASL
     if (client->sasl.conn &&
         client->sasl.username) {
@@ -325,12 +321,10 @@ static VncClientInfo *qmp_query_vnc_client(const VncState *client)
     info->base = binfo;
     info->base->websocket = client->websocket;
 
-#ifdef CONFIG_VNC_TLS
-    if (client->tls.session && client->tls.dname) {
-        info->has_x509_dname = true;
-        info->x509_dname = g_strdup(client->tls.dname);
+    if (client->tls) {
+        info->x509_dname = qcrypto_tls_session_get_peer_name(client->tls);
+        info->has_x509_dname = info->x509_dname != NULL;
     }
-#endif
 #ifdef CONFIG_VNC_SASL
     if (client->sasl.conn && client->sasl.username) {
         info->has_sasl_username = true;
@@ -456,7 +450,6 @@ static void qmp_query_auth(VncDisplay *vd, VncInfo2 *info)
         break;
     case VNC_AUTH_VENCRYPT:
         info->auth = VNC_PRIMARY_AUTH_VENCRYPT;
-#ifdef CONFIG_VNC_TLS
         info->has_vencrypt = true;
         switch (vd->subauth) {
         case VNC_AUTH_VENCRYPT_PLAIN:
@@ -490,7 +483,6 @@ static void qmp_query_auth(VncDisplay *vd, VncInfo2 *info)
             info->has_vencrypt = false;
             break;
         }
-#endif
         break;
     case VNC_AUTH_SASL:
         info->auth = VNC_PRIMARY_AUTH_SASL;
@@ -1140,9 +1132,6 @@ void vnc_disconnect_finish(VncState *vs)
     vnc_tight_clear(vs);
     vnc_zrle_clear(vs);
 
-#ifdef CONFIG_VNC_TLS
-    vnc_tls_client_cleanup(vs);
-#endif /* CONFIG_VNC_TLS */
 #ifdef CONFIG_VNC_SASL
     vnc_sasl_client_cleanup(vs);
 #endif /* CONFIG_VNC_SASL */
@@ -1202,26 +1191,6 @@ void vnc_client_error(VncState *vs)
     vnc_disconnect_start(vs);
 }
 
-#ifdef CONFIG_VNC_TLS
-static ssize_t vnc_client_write_tls(gnutls_session_t *session,
-                                    const uint8_t *data,
-                                    size_t datalen,
-                                    Error **errp)
-{
-    ssize_t ret = gnutls_write(*session, data, datalen);
-    if (ret < 0) {
-        if (ret == GNUTLS_E_AGAIN) {
-            ret = -2;
-        } else {
-            ret = -1;
-            error_setg_errno(errp, -EIO, "%s",
-                             _("Cannot write to TLS socket"));
-        }
-    }
-    return ret;
-}
-#endif /* CONFIG_VNC_TLS */
-
 /*
  * Called to write a chunk of data to the client socket. The data may
  * be the raw data, or may have already been encoded by SASL.
@@ -1241,16 +1210,8 @@ ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
 {
     ssize_t ret;
     Error *err = NULL;
-#ifdef CONFIG_VNC_TLS
-    if (vs->tls.session) {
-        ret = vnc_client_write_tls(&vs->tls.session, data, datalen, &err);
-    } else {
-#endif /* CONFIG_VNC_TLS */
-        ret = qio_channel_write(
-            vs->ioc, (const char *)data, datalen, &err);
-#ifdef CONFIG_VNC_TLS
-    }
-#endif /* CONFIG_VNC_TLS */
+    ret = qio_channel_write(
+        vs->ioc, (const char *)data, datalen, &err);
     VNC_DEBUG("Wrote wire %p %zd -> %ld\n", data, datalen, ret);
     return vnc_client_io_error(vs, ret, &err);
 }
@@ -1346,24 +1307,6 @@ void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting)
     vs->read_handler_expect = expecting;
 }
 
-#ifdef CONFIG_VNC_TLS
-static ssize_t vnc_client_read_tls(gnutls_session_t *session, uint8_t *data,
-                                   size_t datalen, Error **errp)
-{
-    ssize_t ret = gnutls_read(*session, data, datalen);
-    if (ret < 0) {
-        if (ret == GNUTLS_E_AGAIN) {
-            ret = QIO_CHANNEL_ERR_BLOCK;
-        } else {
-            ret = -1;
-            error_setg_errno(errp, -EIO, "%s",
-                             _("Cannot read from TLS socket"));
-        }
-    }
-    return ret;
-}
-#endif /* CONFIG_VNC_TLS */
-
 /*
  * Called to read a chunk of data from the client socket. The data may
  * be the raw data, or may need to be further decoded by SASL.
@@ -1383,16 +1326,8 @@ ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
 {
     ssize_t ret;
     Error *err = NULL;
-#ifdef CONFIG_VNC_TLS
-    if (vs->tls.session) {
-        ret = vnc_client_read_tls(&vs->tls.session, data, datalen, &err);
-    } else {
-#endif /* CONFIG_VNC_TLS */
-        ret = qio_channel_read(
-            vs->ioc, (char *)data, datalen, &err);
-#ifdef CONFIG_VNC_TLS
-    }
-#endif /* CONFIG_VNC_TLS */
+    ret = qio_channel_read(
+        vs->ioc, (char *)data, datalen, &err);
     VNC_DEBUG("Read wire %p %zd -> %ld\n", data, datalen, ret);
     return vnc_client_io_error(vs, ret, &err);
 }
@@ -2559,12 +2494,10 @@ static int protocol_client_auth(VncState *vs, uint8_t *data, size_t len)
            start_auth_vnc(vs);
            break;
 
-#ifdef CONFIG_VNC_TLS
        case VNC_AUTH_VENCRYPT:
            VNC_DEBUG("Accept VeNCrypt auth\n");
            start_auth_vencrypt(vs);
            break;
-#endif /* CONFIG_VNC_TLS */
 
 #ifdef CONFIG_VNC_SASL
        case VNC_AUTH_SASL:
@@ -2946,8 +2879,8 @@ static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc,
             vs->subauth = vd->subauth;
         }
     }
-    VNC_DEBUG("Client sock=%d ws=%d auth=%d subauth=%d\n",
-              csock, websocket, vs->auth, vs->subauth);
+    VNC_DEBUG("Client sock=%p ws=%d auth=%d subauth=%d\n",
+              sioc, websocket, vs->auth, vs->subauth);
 
     vs->lossy_rect = g_malloc0(VNC_STAT_ROWS * sizeof (*vs->lossy_rect));
     for (i = 0; i < VNC_STAT_ROWS; ++i) {
@@ -2959,13 +2892,10 @@ static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc,
     qio_channel_set_blocking(vs->ioc, false);
     if (websocket) {
         vs->websocket = 1;
-#ifdef CONFIG_VNC_TLS
         if (vd->ws_tls) {
             vs->ioc_tag = qio_channel_add_watch(
                 vs->ioc, G_IO_IN, vncws_tls_handshake_io, vs);
-        } else
-#endif /* CONFIG_VNC_TLS */
-        {
+        } else {
             vs->ioc_tag = qio_channel_add_watch(
                 vs->ioc, G_IO_IN, vncws_handshake_io, vs);
         }
@@ -3116,9 +3046,11 @@ static void vnc_display_close(VncDisplay *vs)
     }
     vs->auth = VNC_AUTH_INVALID;
     vs->subauth = VNC_AUTH_INVALID;
-#ifdef CONFIG_VNC_TLS
-    vs->tls.x509verify = 0;
-#endif
+    /** XXX unregister transient creds from global namespace?,
+     * or does it go away automatically on last unref ?*/
+    object_unref(OBJECT(vs->creds));
+    g_free(vs->tlsaclname);
+    vs->tlsaclname = NULL;
 }
 
 int vnc_display_password(const char *id, const char *password)
@@ -3172,6 +3104,9 @@ static QemuOptsList qemu_vnc_opts = {
             .name = "websocket",
             .type = QEMU_OPT_STRING,
         },{
+            .name = "tls-cred",
+            .type = QEMU_OPT_STRING,
+        },{
             .name = "x509",
             .type = QEMU_OPT_STRING,
         },{
@@ -3228,13 +3163,12 @@ static QemuOptsList qemu_vnc_opts = {
 };
 
 
-static void
+static int
 vnc_display_setup_auth(VncDisplay *vs,
                        bool password,
                        bool sasl,
-                       bool tls,
-                       bool x509,
-                       bool websocket)
+                       bool websocket,
+                       Error **errp)
 {
     /*
      * We have a choice of 3 authentication options
@@ -3284,17 +3218,27 @@ vnc_display_setup_auth(VncDisplay *vs,
      * result has the same security characteristics.
      */
     if (password) {
-        if (tls) {
+        if (vs->creds) {
             vs->auth = VNC_AUTH_VENCRYPT;
             if (websocket) {
                 vs->ws_tls = true;
             }
-            if (x509) {
+            switch (vs->creds->type) {
+            case QCRYPTO_TLS_CREDS_TYPE_X509:
                 VNC_DEBUG("Initializing VNC server with x509 password auth\n");
                 vs->subauth = VNC_AUTH_VENCRYPT_X509VNC;
-            } else {
+                break;
+
+            case QCRYPTO_TLS_CREDS_TYPE_ANON:
                 VNC_DEBUG("Initializing VNC server with TLS password auth\n");
                 vs->subauth = VNC_AUTH_VENCRYPT_TLSVNC;
+                break;
+
+            default:
+                error_setg(errp,
+                           _("Unsupported TLS cred type %d"),
+                           vs->creds->type);
+                return -1;
             }
         } else {
             VNC_DEBUG("Initializing VNC server with password auth\n");
@@ -3307,17 +3251,27 @@ vnc_display_setup_auth(VncDisplay *vs,
             vs->ws_auth = VNC_AUTH_INVALID;
         }
     } else if (sasl) {
-        if (tls) {
+        if (vs->creds) {
             vs->auth = VNC_AUTH_VENCRYPT;
             if (websocket) {
                 vs->ws_tls = true;
             }
-            if (x509) {
+            switch (vs->creds->type) {
+            case QCRYPTO_TLS_CREDS_TYPE_X509:
                 VNC_DEBUG("Initializing VNC server with x509 SASL auth\n");
                 vs->subauth = VNC_AUTH_VENCRYPT_X509SASL;
-            } else {
+                break;
+
+            case QCRYPTO_TLS_CREDS_TYPE_ANON:
                 VNC_DEBUG("Initializing VNC server with TLS SASL auth\n");
                 vs->subauth = VNC_AUTH_VENCRYPT_TLSSASL;
+                break;
+
+            default:
+                error_setg(errp,
+                           _("Unsupported TLS cred type %d"),
+                           vs->creds->type);
+                return -1;
             }
         } else {
             VNC_DEBUG("Initializing VNC server with SASL auth\n");
@@ -3330,17 +3284,27 @@ vnc_display_setup_auth(VncDisplay *vs,
             vs->ws_auth = VNC_AUTH_INVALID;
         }
     } else {
-        if (tls) {
+        if (vs->creds) {
             vs->auth = VNC_AUTH_VENCRYPT;
             if (websocket) {
                 vs->ws_tls = true;
             }
-            if (x509) {
+            switch (vs->creds->type) {
+            case QCRYPTO_TLS_CREDS_TYPE_X509:
                 VNC_DEBUG("Initializing VNC server with x509 no auth\n");
                 vs->subauth = VNC_AUTH_VENCRYPT_X509NONE;
-            } else {
+                break;
+
+            case QCRYPTO_TLS_CREDS_TYPE_ANON:
                 VNC_DEBUG("Initializing VNC server with TLS no auth\n");
                 vs->subauth = VNC_AUTH_VENCRYPT_TLSNONE;
+                break;
+
+            default:
+                error_setg(errp,
+                           _("Unsupported TLS cred type %d"),
+                           vs->creds->type);
+                return -1;
             }
         } else {
             VNC_DEBUG("Initializing VNC server with no auth\n");
@@ -3353,6 +3317,43 @@ vnc_display_setup_auth(VncDisplay *vs,
             vs->ws_auth = VNC_AUTH_INVALID;
         }
     }
+    return 0;
+}
+
+static QCryptoTLSCreds *vnc_display_create_creds(bool x509,
+                                                 bool x509verify,
+                                                 const char *dir,
+                                                 const char *id,
+                                                 Error **errp)
+{
+    gchar *credsid = g_strdup_printf("tlsvnc%s", id);
+    Object *creds;
+
+    if (x509) {
+        creds = object_new_propv(TYPE_QCRYPTO_TLS_CREDS,
+                                 credsid,
+                                 errp,
+                                 "credtype", "x509",
+                                 "endpoint", "server",
+                                 "dir", dir,
+                                 "verify-peer", x509verify ? "yes" : "no",
+                                 NULL);
+    } else {
+        creds = object_new_propv(TYPE_QCRYPTO_TLS_CREDS,
+                                 credsid,
+                                 errp,
+                                 "credtype", "anon",
+                                 "endpoint", "server",
+                                 NULL);
+    }
+
+    g_free(credsid);
+
+    if (*errp) {
+        return NULL;
+    }
+
+    return QCRYPTO_TLS_CREDS(creds);
 }
 
 void vnc_display_open(const char *id, Error **errp)
@@ -3369,18 +3370,13 @@ void vnc_display_open(const char *id, Error **errp)
     char *h;
     bool has_ipv4 = false;
     bool has_ipv6 = false;
+    const char *credid;
     const char *websocket;
-    bool tls = false, x509 = false;
-#ifdef CONFIG_VNC_TLS
-    const char *path;
-#endif
     bool sasl = false;
 #ifdef CONFIG_VNC_SASL
     int saslErr;
 #endif
-#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL)
     int acl = 0;
-#endif
     int lock_key_sync = 1;
     Error *err = NULL;
 
@@ -3455,32 +3451,55 @@ void vnc_display_open(const char *id, Error **errp)
         goto fail;
     }
 #endif /* CONFIG_VNC_SASL */
-    tls  = qemu_opt_get_bool(opts, "tls", false);
-#ifdef CONFIG_VNC_TLS
-    path = qemu_opt_get(opts, "x509");
-    if (!path) {
-        path = qemu_opt_get(opts, "x509verify");
-        if (path) {
-            vs->tls.x509verify = true;
-        }
-    }
-    if (path) {
-        x509 = true;
-        if (vnc_tls_set_x509_creds_dir(vs, path) < 0) {
-            error_setg(errp, "Failed to find x509 certificates/keys in %s",
-                       path);
+    credid = qemu_opt_get(opts, "tls-cred");
+    if (credid) {
+        Object *container;
+        if (qemu_opt_get(opts, "tls") ||
+            qemu_opt_get(opts, "x509") ||
+            qemu_opt_get(opts, "x509verify")) {
+            error_setg(errp, "%s",
+                       _("'credid' parameter is mutually exclusive with "
+                         "'tls', 'x509' and 'x509verify' parameters"));
             goto fail;
         }
+
+        container = container_get(object_get_root(), "/objects");
+        /* XXX this type cast causes an abort if the type is wrong.
+         * This is bad. We should check and set an error */
+        vs->creds = QCRYPTO_TLS_CREDS(
+            object_resolve_path_component(container, credid));
+        if (!vs->creds) {
+            error_setg(errp, _("No TLS credentials with id '%s'"),
+                       credid);
+            goto fail;
+        }
+        object_ref(OBJECT(vs->creds));
+    } else {
+        const char *path;
+        bool tls = false, x509 = false, x509verify = false;
+        tls  = qemu_opt_get_bool(opts, "tls", false);
+        if (tls) {
+            path = qemu_opt_get(opts, "x509");
+            if (path) {
+                x509 = true;
+            } else {
+                path = qemu_opt_get(opts, "x509verify");
+                if (path) {
+                    x509 = true;
+                    x509verify = true;
+                }
+            }
+            vs->creds = vnc_display_create_creds(x509,
+                                                 x509verify,
+                                                 path,
+                                                 vs->id,
+                                                 errp);
+            if (!vs->creds) {
+                goto fail;
+            }
+        }
     }
-#else /* ! CONFIG_VNC_TLS */
-    if (tls) {
-        error_setg(errp, "VNC TLS auth requires gnutls support");
-        goto fail;
-    }
-#endif /* ! CONFIG_VNC_TLS */
-#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL)
     acl = qemu_opt_get_bool(opts, "acl", false);
-#endif
 
     share = qemu_opt_get(opts, "share");
     if (share) {
@@ -3520,19 +3539,14 @@ void vnc_display_open(const char *id, Error **errp)
         vs->non_adaptive = true;
     }
 
-#ifdef CONFIG_VNC_TLS
-    if (acl && x509 && vs->tls.x509verify) {
-        char *aclname;
-
+    if (acl) {
         if (strcmp(vs->id, "default") == 0) {
-            aclname = g_strdup("vnc.x509dname");
+            vs->tlsaclname = g_strdup("vnc.x509dname");
         } else {
-            aclname = g_strdup_printf("vnc.%s.x509dname", vs->id);
+            vs->tlsaclname = g_strdup_printf("vnc.%s.x509dname", vs->id);
         }
-        vs->tls.acl = qemu_acl_init(aclname);
-        g_free(aclname);
+        qemu_acl_init(vs->tlsaclname);
     }
-#endif
 #ifdef CONFIG_VNC_SASL
     if (acl && sasl) {
         char *aclname;
@@ -3547,7 +3561,9 @@ void vnc_display_open(const char *id, Error **errp)
     }
 #endif
 
-    vnc_display_setup_auth(vs, password, sasl, tls, x509, websocket);
+    if (vnc_display_setup_auth(vs, password, sasl, websocket, errp) < 0) {
+        goto fail;
+    }
 
 #ifdef CONFIG_VNC_SASL
     if ((saslErr = sasl_server_init(NULL, "qemu")) != SASL_OK) {
diff --git a/ui/vnc.h b/ui/vnc.h
index 593e6b6..008489b 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -35,6 +35,7 @@
 #include "qemu/bitmap.h"
 #include "io/buffer.h"
 #include "io/channel-socket.h"
+#include "io/channel-tls.h"
 #include <zlib.h>
 #include <stdbool.h>
 
@@ -96,10 +97,7 @@ typedef void VncSendHextileTile(VncState *vs,
 
 typedef struct VncDisplay VncDisplay;
 
-#ifdef CONFIG_VNC_TLS
-#include "vnc-tls.h"
 #include "vnc-auth-vencrypt.h"
-#endif
 #ifdef CONFIG_VNC_SASL
 #include "vnc-auth-sasl.h"
 #endif
@@ -178,9 +176,8 @@ struct VncDisplay
     bool ws_tls; /* Used by websockets */
     bool lossy;
     bool non_adaptive;
-#ifdef CONFIG_VNC_TLS
-    VncDisplayTLS tls;
-#endif
+    QCryptoTLSCreds *creds;
+    char *tlsaclname;
 #ifdef CONFIG_VNC_SASL
     VncDisplaySASL sasl;
 #endif
@@ -284,9 +281,7 @@ struct VncState
     int auth;
     int subauth; /* Used by VeNCrypt */
     char challenge[VNC_AUTH_CHALLENGE_SIZE];
-#ifdef CONFIG_VNC_TLS
-    VncStateTLS tls;
-#endif
+    QCryptoTLSSession *tls; /* Borrowed pointer from channel, don't free */
 #ifdef CONFIG_VNC_SASL
     VncStateSASL sasl;
 #endif
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 31/34] ui: convert VNC server to use QIOChannelWebsock
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (29 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 30/34] ui: convert VNC server to use QIOChannelTLS Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 32/34] char: convert from GIOChannel to QIOChannel Daniel P. Berrange
                   ` (3 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

Remove custom websock handling code from the VNC server and use
the QIOChannelWebsock class instead.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 ui/vnc-ws.c | 329 +++++-------------------------------------------------------
 ui/vnc-ws.h |  63 ------------
 ui/vnc.c    |  25 +----
 ui/vnc.h    |   4 -
 4 files changed, 29 insertions(+), 392 deletions(-)

diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c
index 714e513..a0e3cae 100644
--- a/ui/vnc-ws.c
+++ b/ui/vnc-ws.c
@@ -19,12 +19,7 @@
  */
 
 #include "vnc.h"
-#include "qemu/main-loop.h"
-#include "crypto/hash.h"
-
-#include "qemu/sockets.h"
-
-static void vncws_handshake_read(VncState *vs);
+#include "io/channel-websock.h"
 
 static void vncws_tls_handshake_done(QIOTask *task,
                                      gpointer user_data)
@@ -35,6 +30,7 @@ static void vncws_tls_handshake_done(QIOTask *task,
         VNC_DEBUG("Handshake failed %s\n", error_get_pretty(task->err));
         vnc_client_error(vs);
     } else {
+        VNC_DEBUG("TLS handshake complete, starting websocket handshake\n");
         vs->ioc_tag = qio_channel_add_watch(
             QIO_CHANNEL(vs->ioc), G_IO_IN, vncws_handshake_io, vs);
     }
@@ -79,38 +75,20 @@ gboolean vncws_tls_handshake_io(QIOChannel *ioc G_GNUC_UNUSED,
     return TRUE;
 }
 
-static void vncws_handshake_read(VncState *vs)
-{
-    uint8_t *handshake_end;
-    long ret;
-    /* Typical HTTP headers from novnc are 512 bytes, so limiting
-     * total header size to 4096 is easily enough. */
-    size_t want = 4096 - vs->ws_input.offset;
-    qio_buffer_reserve(&vs->ws_input, want);
-    ret = vnc_client_read_buf(vs, qio_buffer_end(&vs->ws_input), want);
 
-    if (!ret) {
-        if (vs->disconnecting) {
-            vnc_disconnect_finish(vs);
-        }
-        return;
-    }
-    vs->ws_input.offset += ret;
+static void vncws_handshake_done(QIOTask *task,
+                                 gpointer user_data)
+{
+    struct VncState *vs = user_data;
 
-    handshake_end = (uint8_t *)g_strstr_len((char *)vs->ws_input.buffer,
-            vs->ws_input.offset, WS_HANDSHAKE_END);
-    if (handshake_end) {
-        if (vs->ioc_tag) {
-            g_source_remove(vs->ioc_tag);
-        }
+    if (task->err) {
+        VNC_DEBUG("Websock handshake failed %s\n", error_get_pretty(task->err));
+        vnc_client_error(vs);
+    } else {
+        VNC_DEBUG("Websock handshake complete, starting VNC protocol\n");
+        vnc_init_state(vs);
         vs->ioc_tag = qio_channel_add_watch(
             vs->ioc, G_IO_IN, vnc_client_io, vs);
-        vncws_process_handshake(vs, vs->ws_input.buffer, vs->ws_input.offset);
-        qio_buffer_advance(&vs->ws_input, handshake_end - vs->ws_input.buffer +
-                strlen(WS_HANDSHAKE_END));
-    } else if (vs->ws_input.offset >= 4096) {
-        VNC_DEBUG("End of headers not found in first 4096 bytes\n");
-        vnc_client_error(vs);
     }
 }
 
@@ -120,280 +98,23 @@ gboolean vncws_handshake_io(QIOChannel *ioc G_GNUC_UNUSED,
                             void *opaque)
 {
     VncState *vs = opaque;
-    vncws_handshake_read(vs);
-    return TRUE;
-}
-
-long vnc_client_read_ws(VncState *vs)
-{
-    int ret, err;
-    uint8_t *payload;
-    size_t payload_size, header_size;
-    VNC_DEBUG("Read websocket %p size %zd offset %zd\n", vs->ws_input.buffer,
-            vs->ws_input.capacity, vs->ws_input.offset);
-    qio_buffer_reserve(&vs->ws_input, 4096);
-    ret = vnc_client_read_buf(vs, qio_buffer_end(&vs->ws_input), 4096);
-    if (!ret) {
-        return 0;
-    }
-    vs->ws_input.offset += ret;
-
-    ret = 0;
-    /* consume as much of ws_input buffer as possible */
-    do {
-        if (vs->ws_payload_remain == 0) {
-            err = vncws_decode_frame_header(&vs->ws_input,
-                                            &header_size,
-                                            &vs->ws_payload_remain,
-                                            &vs->ws_payload_mask);
-            if (err <= 0) {
-                return err;
-            }
-
-            qio_buffer_advance(&vs->ws_input, header_size);
-        }
-        if (vs->ws_payload_remain != 0) {
-            err = vncws_decode_frame_payload(&vs->ws_input,
-                                             &vs->ws_payload_remain,
-                                             &vs->ws_payload_mask,
-                                             &payload,
-                                             &payload_size);
-            if (err < 0) {
-                return err;
-            }
-            if (err == 0) {
-                return ret;
-            }
-            ret += err;
-
-            qio_buffer_reserve(&vs->input, payload_size);
-            qio_buffer_append(&vs->input, payload, payload_size);
-
-            qio_buffer_advance(&vs->ws_input, payload_size);
-        }
-    } while (vs->ws_input.offset > 0);
-
-    return ret;
-}
-
-long vnc_client_write_ws(VncState *vs)
-{
-    long ret;
-    VNC_DEBUG("Write WS: Pending output %p size %zd offset %zd\n",
-              vs->output.buffer, vs->output.capacity, vs->output.offset);
-    vncws_encode_frame(&vs->ws_output, vs->output.buffer, vs->output.offset);
-    qio_buffer_reset(&vs->output);
-    ret = vnc_client_write_buf(vs, vs->ws_output.buffer, vs->ws_output.offset);
-    if (!ret) {
-        return 0;
-    }
-
-    qio_buffer_advance(&vs->ws_output, ret);
-
-    if (vs->ws_output.offset == 0) {
-        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);
-    }
-
-    return ret;
-}
-
-static char *vncws_extract_handshake_entry(const char *handshake,
-        size_t handshake_len, const char *name)
-{
-    char *begin, *end, *ret = NULL;
-    char *line = g_strdup_printf("%s%s: ", WS_HANDSHAKE_DELIM, name);
-    begin = g_strstr_len(handshake, handshake_len, line);
-    if (begin != NULL) {
-        begin += strlen(line);
-        end = g_strstr_len(begin, handshake_len - (begin - handshake),
-                WS_HANDSHAKE_DELIM);
-        if (end != NULL) {
-            ret = g_strndup(begin, end - begin);
-        }
-    }
-    g_free(line);
-    return ret;
-}
-
-static void vncws_send_handshake_response(VncState *vs, const char* key)
-{
-    char combined_key[WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1];
-    char *accept = NULL, *response = NULL;
-    Error *err = NULL;
-
-    g_strlcpy(combined_key, key, WS_CLIENT_KEY_LEN + 1);
-    g_strlcat(combined_key, WS_GUID, WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1);
-
-    /* hash and encode it */
-    if (qcrypto_hash_base64(QCRYPTO_HASH_ALG_SHA1,
-                            combined_key,
-                            WS_CLIENT_KEY_LEN + WS_GUID_LEN,
-                            &accept,
-                            &err) < 0) {
-        VNC_DEBUG("Hashing Websocket combined key failed %s\n",
-                  error_get_pretty(err));
-        error_free(err);
-        vnc_client_error(vs);
-        return;
-    }
-
-    response = g_strdup_printf(WS_HANDSHAKE, accept);
-    vnc_client_write_buf(vs, (const uint8_t *)response, strlen(response));
+    QIOChannelWebsock *wioc;
 
-    g_free(accept);
-    g_free(response);
-
-    vs->encode_ws = 1;
-    vnc_init_state(vs);
-}
-
-void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size)
-{
-    char *protocols = vncws_extract_handshake_entry((const char *)line, size,
-            "Sec-WebSocket-Protocol");
-    char *version = vncws_extract_handshake_entry((const char *)line, size,
-            "Sec-WebSocket-Version");
-    char *key = vncws_extract_handshake_entry((const char *)line, size,
-            "Sec-WebSocket-Key");
-
-    if (protocols && version && key
-            && g_strrstr(protocols, "binary")
-            && !strcmp(version, WS_SUPPORTED_VERSION)
-            && strlen(key) == WS_CLIENT_KEY_LEN) {
-        vncws_send_handshake_response(vs, key);
-    } else {
-        VNC_DEBUG("Defective Websockets header or unsupported protocol\n");
-        vnc_client_error(vs);
-    }
-
-    g_free(protocols);
-    g_free(version);
-    g_free(key);
-}
-
-void vncws_encode_frame(QIOBuffer *output, const void *payload,
-                        const size_t payload_size)
-{
-    size_t header_size = 0;
-    unsigned char opcode = WS_OPCODE_BINARY_FRAME;
-    union {
-        char buf[WS_HEAD_MAX_LEN];
-        WsHeader ws;
-    } header;
-
-    if (!payload_size) {
-        return;
-    }
-
-    header.ws.b0 = 0x80 | (opcode & 0x0f);
-    if (payload_size <= 125) {
-        header.ws.b1 = (uint8_t)payload_size;
-        header_size = 2;
-    } else if (payload_size < 65536) {
-        header.ws.b1 = 0x7e;
-        header.ws.u.s16.l16 = cpu_to_be16((uint16_t)payload_size);
-        header_size = 4;
-    } else {
-        header.ws.b1 = 0x7f;
-        header.ws.u.s64.l64 = cpu_to_be64(payload_size);
-        header_size = 10;
-    }
-
-    qio_buffer_reserve(output, header_size + payload_size);
-    qio_buffer_append(output, header.buf, header_size);
-    qio_buffer_append(output, payload, payload_size);
-}
-
-int vncws_decode_frame_header(QIOBuffer *input,
-                              size_t *header_size,
-                              size_t *payload_remain,
-                              WsMask *payload_mask)
-{
-    unsigned char opcode = 0, fin = 0, has_mask = 0;
-    size_t payload_len;
-    WsHeader *header = (WsHeader *)input->buffer;
-
-    if (input->offset < WS_HEAD_MIN_LEN + 4) {
-        /* header not complete */
-        return 0;
-    }
-
-    fin = (header->b0 & 0x80) >> 7;
-    opcode = header->b0 & 0x0f;
-    has_mask = (header->b1 & 0x80) >> 7;
-    payload_len = header->b1 & 0x7f;
-
-    if (opcode == WS_OPCODE_CLOSE) {
-        /* disconnect */
-        return -1;
-    }
-
-    /* Websocket frame sanity check:
-     * * Websocket fragmentation is not supported.
-     * * All  websockets frames sent by a client have to be masked.
-     * * Only binary encoding is supported.
-     */
-    if (!fin || !has_mask || opcode != WS_OPCODE_BINARY_FRAME) {
-        VNC_DEBUG("Received faulty/unsupported Websocket frame\n");
-        return -2;
+    VNC_DEBUG("Websocket negotiate starting\n");
+    if (vs->ioc_tag) {
+        g_source_remove(vs->ioc_tag);
+        vs->ioc_tag = 0;
     }
 
-    if (payload_len < 126) {
-        *payload_remain = payload_len;
-        *header_size = 6;
-        *payload_mask = header->u.m;
-    } else if (payload_len == 126 && input->offset >= 8) {
-        *payload_remain = be16_to_cpu(header->u.s16.l16);
-        *header_size = 8;
-        *payload_mask = header->u.s16.m16;
-    } else if (payload_len == 127 && input->offset >= 14) {
-        *payload_remain = be64_to_cpu(header->u.s64.l64);
-        *header_size = 14;
-        *payload_mask = header->u.s64.m64;
-    } else {
-        /* header not complete */
-        return 0;
-    }
+    wioc = qio_channel_websock_new_server(vs->ioc);
 
-    return 1;
-}
-
-int vncws_decode_frame_payload(QIOBuffer *input,
-                               size_t *payload_remain, WsMask *payload_mask,
-                               uint8_t **payload, size_t *payload_size)
-{
-    size_t i;
-    uint32_t *payload32;
-
-    *payload = input->buffer;
-    /* If we aren't at the end of the payload, then drop
-     * off the last bytes, so we're always multiple of 4
-     * for purpose of unmasking, except at end of payload
-     */
-    if (input->offset < *payload_remain) {
-        *payload_size = input->offset - (input->offset % 4);
-    } else {
-        *payload_size = *payload_remain;
-    }
-    if (*payload_size == 0) {
-        return 0;
-    }
-    *payload_remain -= *payload_size;
+    object_unref(OBJECT(vs->ioc));
+    vs->ioc = QIO_CHANNEL(wioc);
 
-    /* unmask frame */
-    /* process 1 frame (32 bit op) */
-    payload32 = (uint32_t *)(*payload);
-    for (i = 0; i < *payload_size / 4; i++) {
-        payload32[i] ^= payload_mask->u;
-    }
-    /* process the remaining bytes (if any) */
-    for (i *= 4; i < *payload_size; i++) {
-        (*payload)[i] ^= payload_mask->c[i % 4];
-    }
+    qio_channel_websock_handshake(wioc,
+                                  vncws_handshake_done,
+                                  vs,
+                                  NULL);
 
-    return 1;
+    return TRUE;
 }
diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h
index 21caaf3..652b6fc 100644
--- a/ui/vnc-ws.h
+++ b/ui/vnc-ws.h
@@ -21,74 +21,11 @@
 #ifndef __QEMU_UI_VNC_WS_H
 #define __QEMU_UI_VNC_WS_H
 
-#define B64LEN(__x) (((__x + 2) / 3) * 12 / 3)
-#define SHA1_DIGEST_LEN 20
-
-#define WS_ACCEPT_LEN (B64LEN(SHA1_DIGEST_LEN) + 1)
-#define WS_CLIENT_KEY_LEN 24
-#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
-#define WS_GUID_LEN strlen(WS_GUID)
-
-#define WS_HANDSHAKE "HTTP/1.1 101 Switching Protocols\r\n\
-Upgrade: websocket\r\n\
-Connection: Upgrade\r\n\
-Sec-WebSocket-Accept: %s\r\n\
-Sec-WebSocket-Protocol: binary\r\n\
-\r\n"
-#define WS_HANDSHAKE_DELIM "\r\n"
-#define WS_HANDSHAKE_END "\r\n\r\n"
-#define WS_SUPPORTED_VERSION "13"
-
-#define WS_HEAD_MIN_LEN sizeof(uint16_t)
-#define WS_HEAD_MAX_LEN (WS_HEAD_MIN_LEN + sizeof(uint64_t) + sizeof(uint32_t))
-
-typedef union WsMask {
-    char c[4];
-    uint32_t u;
-} WsMask;
-
-typedef struct QEMU_PACKED WsHeader {
-    unsigned char b0;
-    unsigned char b1;
-    union {
-        struct QEMU_PACKED {
-            uint16_t l16;
-            WsMask m16;
-        } s16;
-        struct QEMU_PACKED {
-            uint64_t l64;
-            WsMask m64;
-        } s64;
-        WsMask m;
-    } u;
-} WsHeader;
-
-enum {
-    WS_OPCODE_CONTINUATION = 0x0,
-    WS_OPCODE_TEXT_FRAME = 0x1,
-    WS_OPCODE_BINARY_FRAME = 0x2,
-    WS_OPCODE_CLOSE = 0x8,
-    WS_OPCODE_PING = 0x9,
-    WS_OPCODE_PONG = 0xA
-};
-
 gboolean vncws_tls_handshake_io(QIOChannel *ioc,
                                 GIOCondition condition,
                                 void *opaque);
 gboolean vncws_handshake_io(QIOChannel *ioc,
                             GIOCondition condition,
                             void *opaque);
-long vnc_client_write_ws(VncState *vs);
-long vnc_client_read_ws(VncState *vs);
-void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size);
-void vncws_encode_frame(QIOBuffer *output, const void *payload,
-            const size_t payload_size);
-int vncws_decode_frame_header(QIOBuffer *input,
-                              size_t *header_size,
-                              size_t *payload_remain,
-                              WsMask *payload_mask);
-int vncws_decode_frame_payload(QIOBuffer *input,
-                               size_t *payload_remain, WsMask *payload_mask,
-                               uint8_t **payload, size_t *payload_size);
 
 #endif /* __QEMU_UI_VNC_WS_H */
diff --git a/ui/vnc.c b/ui/vnc.c
index 65d7493..131d5fe 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -1123,8 +1123,6 @@ void vnc_disconnect_finish(VncState *vs)
 
     qio_buffer_free(&vs->input);
     qio_buffer_free(&vs->output);
-    qio_buffer_free(&vs->ws_input);
-    qio_buffer_free(&vs->ws_output);
 
     qapi_free_VncClientInfo(vs->info);
 
@@ -1277,11 +1275,7 @@ static void vnc_client_write_locked(VncState *vs)
     } else
 #endif /* CONFIG_VNC_SASL */
     {
-        if (vs->encode_ws) {
-            vnc_client_write_ws(vs);
-        } else {
-            vnc_client_write_plain(vs);
-        }
+        vnc_client_write_plain(vs);
     }
 }
 
@@ -1289,7 +1283,7 @@ static void vnc_client_write(VncState *vs)
 {
 
     vnc_lock_output(vs);
-    if (vs->output.offset || vs->ws_output.offset) {
+    if (vs->output.offset) {
         vnc_client_write_locked(vs);
     } else if (vs->ioc != NULL) {
         if (vs->ioc_tag) {
@@ -1375,18 +1369,7 @@ static void vnc_client_read(VncState *vs)
         ret = vnc_client_read_sasl(vs);
     else
 #endif /* CONFIG_VNC_SASL */
-        if (vs->encode_ws) {
-            ret = vnc_client_read_ws(vs);
-            if (ret == -1) {
-                vnc_disconnect_start(vs);
-                return;
-            } else if (ret == -2) {
-                vnc_client_error(vs);
-                return;
-            }
-        } else {
-            ret = vnc_client_read_plain(vs);
-        }
+        ret = vnc_client_read_plain(vs);
     if (!ret) {
         if (vs->disconnecting) {
             vnc_disconnect_finish(vs);
@@ -1476,7 +1459,7 @@ void vnc_write_u8(VncState *vs, uint8_t value)
 void vnc_flush(VncState *vs)
 {
     vnc_lock_output(vs);
-    if (vs->ioc != NULL && (vs->output.offset || vs->ws_output.offset)) {
+    if (vs->ioc != NULL && vs->output.offset) {
         vnc_client_write_locked(vs);
     }
     vnc_unlock_output(vs);
diff --git a/ui/vnc.h b/ui/vnc.h
index 008489b..e0022db 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -292,10 +292,6 @@ struct VncState
 
     QIOBuffer output;
     QIOBuffer input;
-    QIOBuffer ws_input;
-    QIOBuffer ws_output;
-    size_t ws_payload_remain;
-    WsMask ws_payload_mask;
     /* current output mode information */
     VncWritePixels *write_pixels;
     PixelFormat client_pf;
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 32/34] char: convert from GIOChannel to QIOChannel
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (30 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 31/34] ui: convert VNC server to use QIOChannelWebsock Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 33/34] char: don't assume telnet initialization will not block Daniel P. Berrange
                   ` (2 subsequent siblings)
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

In preparation for introducing TLS support to the TCP chardev
backend, convert existing chardev code from using GIOChannel
to QIOChannel. This simplifies the chardev code by removing
most of the OS platform conditional code for dealing with
file descriptor passing.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 qemu-char.c | 601 +++++++++++++++++++++++-------------------------------------
 1 file changed, 232 insertions(+), 369 deletions(-)

diff --git a/qemu-char.c b/qemu-char.c
index a405d76..fada1a7 100644
--- a/qemu-char.c
+++ b/qemu-char.c
@@ -31,6 +31,8 @@
 #include "qapi/qmp-input-visitor.h"
 #include "qapi/qmp-output-visitor.h"
 #include "qapi-visit.h"
+#include "io/channel-socket.h"
+#include "io/channel-file.h"
 
 #include <unistd.h>
 #include <fcntl.h>
@@ -783,7 +785,7 @@ typedef struct IOWatchPoll
 {
     GSource parent;
 
-    GIOChannel *channel;
+    QIOChannel *ioc;
     GSource *src;
 
     IOCanReadHandler *fd_can_read;
@@ -806,7 +808,8 @@ static gboolean io_watch_poll_prepare(GSource *source, gint *timeout_)
     }
 
     if (now_active) {
-        iwp->src = g_io_create_watch(iwp->channel, G_IO_IN | G_IO_ERR | G_IO_HUP);
+        iwp->src = qio_channel_create_watch(
+            iwp->ioc, G_IO_IN | G_IO_ERR | G_IO_HUP);
         g_source_set_callback(iwp->src, iwp->fd_read, iwp->opaque, NULL);
         g_source_attach(iwp->src, NULL);
     } else {
@@ -852,9 +855,9 @@ static GSourceFuncs io_watch_poll_funcs = {
 };
 
 /* Can only be used for read */
-static guint io_add_watch_poll(GIOChannel *channel,
+static guint io_add_watch_poll(QIOChannel *ioc,
                                IOCanReadHandler *fd_can_read,
-                               GIOFunc fd_read,
+                               QIOChannelFunc fd_read,
                                gpointer user_data)
 {
     IOWatchPoll *iwp;
@@ -863,7 +866,7 @@ static guint io_add_watch_poll(GIOChannel *channel,
     iwp = (IOWatchPoll *) g_source_new(&io_watch_poll_funcs, sizeof(IOWatchPoll));
     iwp->fd_can_read = fd_can_read;
     iwp->opaque = user_data;
-    iwp->channel = channel;
+    iwp->ioc = ioc;
     iwp->fd_read = (GSourceFunc) fd_read;
     iwp->src = NULL;
 
@@ -899,79 +902,53 @@ static void remove_fd_in_watch(CharDriverState *chr)
     }
 }
 
-#ifndef _WIN32
-static GIOChannel *io_channel_from_fd(int fd)
-{
-    GIOChannel *chan;
-
-    if (fd == -1) {
-        return NULL;
-    }
-
-    chan = g_io_channel_unix_new(fd);
-
-    g_io_channel_set_encoding(chan, NULL, NULL);
-    g_io_channel_set_buffered(chan, FALSE);
-
-    return chan;
-}
-#endif
 
-static GIOChannel *io_channel_from_socket(int fd)
+static int io_channel_send_full(QIOChannel *ioc,
+                                const void *buf, size_t len,
+                                int *fds, size_t nfds)
 {
-    GIOChannel *chan;
+    size_t offset = 0;
 
-    if (fd == -1) {
-        return NULL;
-    }
+    while (offset < len) {
+        ssize_t ret = 0;
+        struct iovec iov = { .iov_base = (char *)buf + offset,
+                             .iov_len = len - offset };
+
+        ret = qio_channel_writev_full(
+            ioc, &iov, 1,
+            fds, nfds, 0, NULL);
+        if (ret == QIO_CHANNEL_ERR_BLOCK) {
+            errno = EAGAIN;
+            return -1;
+        }
 
-#ifdef _WIN32
-    chan = g_io_channel_win32_new_socket(fd);
-#else
-    chan = g_io_channel_unix_new(fd);
-#endif
+        if (ret > 0) {
+            offset += ret;
+        }
+        if (ret < 0) {
+            if (offset) {
+                return offset;
+            }
 
-    g_io_channel_set_encoding(chan, NULL, NULL);
-    g_io_channel_set_buffered(chan, FALSE);
+            errno = EINVAL;
+            return -1;
+        }
+    }
 
-    return chan;
+    return offset;
 }
 
-static int io_channel_send(GIOChannel *fd, const void *buf, size_t len)
-{
-    size_t offset = 0;
-    GIOStatus status = G_IO_STATUS_NORMAL;
 
-    while (offset < len && status == G_IO_STATUS_NORMAL) {
-        gsize bytes_written = 0;
-
-        status = g_io_channel_write_chars(fd, buf + offset, len - offset,
-                                          &bytes_written, NULL);
-        offset += bytes_written;
-    }
-
-    if (offset > 0) {
-        return offset;
-    }
-    switch (status) {
-    case G_IO_STATUS_NORMAL:
-        g_assert(len == 0);
-        return 0;
-    case G_IO_STATUS_AGAIN:
-        errno = EAGAIN;
-        return -1;
-    default:
-        break;
-    }
-    errno = EINVAL;
-    return -1;
+static int io_channel_send(QIOChannel *ioc, const void *buf, size_t len)
+{
+    return io_channel_send_full(ioc, buf, len, NULL, 0);
 }
 
 #ifndef _WIN32
 
 typedef struct FDCharDriver {
     CharDriverState *chr;
-    GIOChannel *fd_in, *fd_out;
+    QIOChannel *ioc_in, *ioc_out;
     int max_size;
     QTAILQ_ENTRY(FDCharDriver) node;
 } FDCharDriver;
@@ -981,17 +958,16 @@ static int fd_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
 {
     FDCharDriver *s = chr->opaque;
     
-    return io_channel_send(s->fd_out, buf, len);
+    return io_channel_send(s->ioc_out, buf, len);
 }
 
-static gboolean fd_chr_read(GIOChannel *chan, GIOCondition cond, void *opaque)
+static gboolean fd_chr_read(QIOChannel *chan, GIOCondition cond, void *opaque)
 {
     CharDriverState *chr = opaque;
     FDCharDriver *s = chr->opaque;
     int len;
     uint8_t buf[READ_BUF_LEN];
-    GIOStatus status;
-    gsize bytes_read;
+    ssize_t ret;
 
     len = sizeof(buf);
     if (len > s->max_size) {
@@ -1001,15 +977,15 @@ static gboolean fd_chr_read(GIOChannel *chan, GIOCondition cond, void *opaque)
         return TRUE;
     }
 
-    status = g_io_channel_read_chars(chan, (gchar *)buf,
-                                     len, &bytes_read, NULL);
-    if (status == G_IO_STATUS_EOF) {
+    ret = qio_channel_read(
+        chan, (gchar *)buf, len, NULL);
+    if (ret == 0) {
         remove_fd_in_watch(chr);
         qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
         return FALSE;
     }
-    if (status == G_IO_STATUS_NORMAL) {
-        qemu_chr_be_write(chr, buf, bytes_read);
+    if (ret > 0) {
+        qemu_chr_be_write(chr, buf, ret);
     }
 
     return TRUE;
@@ -1027,7 +1003,7 @@ static int fd_chr_read_poll(void *opaque)
 static GSource *fd_chr_add_watch(CharDriverState *chr, GIOCondition cond)
 {
     FDCharDriver *s = chr->opaque;
-    return g_io_create_watch(s->fd_out, cond);
+    return qio_channel_create_watch(s->ioc_out, cond);
 }
 
 static void fd_chr_update_read_handler(CharDriverState *chr)
@@ -1035,8 +1011,9 @@ static void fd_chr_update_read_handler(CharDriverState *chr)
     FDCharDriver *s = chr->opaque;
 
     remove_fd_in_watch(chr);
-    if (s->fd_in) {
-        chr->fd_in_tag = io_add_watch_poll(s->fd_in, fd_chr_read_poll,
+    if (s->ioc_in) {
+        chr->fd_in_tag = io_add_watch_poll(s->ioc_in,
+                                           fd_chr_read_poll,
                                            fd_chr_read, chr);
     }
 }
@@ -1046,11 +1023,11 @@ static void fd_chr_close(struct CharDriverState *chr)
     FDCharDriver *s = chr->opaque;
 
     remove_fd_in_watch(chr);
-    if (s->fd_in) {
-        g_io_channel_unref(s->fd_in);
+    if (s->ioc_in) {
+        object_unref(OBJECT(s->ioc_in));
     }
-    if (s->fd_out) {
-        g_io_channel_unref(s->fd_out);
+    if (s->ioc_out) {
+        object_unref(OBJECT(s->ioc_out));
     }
 
     g_free(s);
@@ -1065,8 +1042,8 @@ static CharDriverState *qemu_chr_open_fd(int fd_in, int fd_out)
 
     chr = qemu_chr_alloc();
     s = g_malloc0(sizeof(FDCharDriver));
-    s->fd_in = io_channel_from_fd(fd_in);
-    s->fd_out = io_channel_from_fd(fd_out);
+    s->ioc_in = QIO_CHANNEL(qio_channel_file_new_fd(fd_in));
+    s->ioc_out = QIO_CHANNEL(qio_channel_file_new_fd(fd_out));
     qemu_set_nonblock(fd_out);
     s->chr = chr;
     chr->opaque = s;
@@ -1199,7 +1176,7 @@ static CharDriverState *qemu_chr_open_stdio(ChardevStdio *opts)
 #define HAVE_CHARDEV_TTY 1
 
 typedef struct {
-    GIOChannel *fd;
+    QIOChannel *ioc;
     int read_bytes;
 
     /* Protected by the CharDriverState chr_write_lock.  */
@@ -1249,8 +1226,9 @@ static void pty_chr_update_read_handler_locked(CharDriverState *chr)
 {
     PtyCharDriver *s = chr->opaque;
     GPollFD pfd;
+    QIOChannelFile *fioc = QIO_CHANNEL_FILE(s->ioc);
 
-    pfd.fd = g_io_channel_unix_get_fd(s->fd);
+    pfd.fd = fioc->fd;
     pfd.events = G_IO_OUT;
     pfd.revents = 0;
     g_poll(&pfd, 1, 0);
@@ -1280,7 +1258,7 @@ static int pty_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
             return 0;
         }
     }
-    return io_channel_send(s->fd, buf, len);
+    return io_channel_send(s->ioc, buf, len);
 }
 
 static GSource *pty_chr_add_watch(CharDriverState *chr, GIOCondition cond)
@@ -1289,7 +1267,7 @@ static GSource *pty_chr_add_watch(CharDriverState *chr, GIOCondition cond)
     if (!s->connected) {
         return NULL;
     }
-    return g_io_create_watch(s->fd, cond);
+    return qio_channel_create_watch(s->ioc, cond);
 }
 
 static int pty_chr_read_poll(void *opaque)
@@ -1301,13 +1279,13 @@ static int pty_chr_read_poll(void *opaque)
     return s->read_bytes;
 }
 
-static gboolean pty_chr_read(GIOChannel *chan, GIOCondition cond, void *opaque)
+static gboolean pty_chr_read(QIOChannel *chan, GIOCondition cond, void *opaque)
 {
     CharDriverState *chr = opaque;
     PtyCharDriver *s = chr->opaque;
-    gsize size, len;
+    gsize len;
     uint8_t buf[READ_BUF_LEN];
-    GIOStatus status;
+    ssize_t ret;
 
     len = sizeof(buf);
     if (len > s->read_bytes)
@@ -1315,13 +1293,13 @@ static gboolean pty_chr_read(GIOChannel *chan, GIOCondition cond, void *opaque)
     if (len == 0) {
         return TRUE;
     }
-    status = g_io_channel_read_chars(s->fd, (gchar *)buf, len, &size, NULL);
-    if (status != G_IO_STATUS_NORMAL) {
+    ret = qio_channel_read(s->ioc, (char *)buf, len, NULL);
+    if (ret <= 0) {
         pty_chr_state(chr, 0);
         return FALSE;
     } else {
         pty_chr_state(chr, 1);
-        qemu_chr_be_write(chr, buf, size);
+        qemu_chr_be_write(chr, buf, ret);
     }
     return TRUE;
 }
@@ -1363,7 +1341,8 @@ static void pty_chr_state(CharDriverState *chr, int connected)
             s->open_tag = g_idle_add(qemu_chr_be_generic_open_func, chr);
         }
         if (!chr->fd_in_tag) {
-            chr->fd_in_tag = io_add_watch_poll(s->fd, pty_chr_read_poll,
+            chr->fd_in_tag = io_add_watch_poll(s->ioc,
+                                               pty_chr_read_poll,
                                                pty_chr_read, chr);
         }
     }
@@ -1372,13 +1351,10 @@ static void pty_chr_state(CharDriverState *chr, int connected)
 static void pty_chr_close(struct CharDriverState *chr)
 {
     PtyCharDriver *s = chr->opaque;
-    int fd;
 
     qemu_mutex_lock(&chr->chr_write_lock);
     pty_chr_state(chr, 0);
-    fd = g_io_channel_unix_get_fd(s->fd);
-    g_io_channel_unref(s->fd);
-    close(fd);
+    object_unref(OBJECT(s->ioc));
     if (s->timer_tag) {
         g_source_remove(s->timer_tag);
         s->timer_tag = 0;
@@ -1421,7 +1397,7 @@ static CharDriverState *qemu_chr_open_pty(const char *id,
     chr->chr_add_watch = pty_chr_add_watch;
     chr->explicit_be_open = true;
 
-    s->fd = io_channel_from_fd(master_fd);
+    s->ioc = QIO_CHANNEL(qio_channel_file_new_fd(master_fd));
     s->timer_tag = 0;
 
     return chr;
@@ -1545,12 +1521,13 @@ static void tty_serial_init(int fd, int speed,
 static int tty_serial_ioctl(CharDriverState *chr, int cmd, void *arg)
 {
     FDCharDriver *s = chr->opaque;
+    QIOChannelFile *fioc = QIO_CHANNEL_FILE(s->ioc_in);
 
     switch(cmd) {
     case CHR_IOCTL_SERIAL_SET_PARAMS:
         {
             QEMUSerialSetParams *ssp = arg;
-            tty_serial_init(g_io_channel_unix_get_fd(s->fd_in),
+            tty_serial_init(fioc->fd,
                             ssp->speed, ssp->parity,
                             ssp->data_bits, ssp->stop_bits);
         }
@@ -1559,7 +1536,7 @@ static int tty_serial_ioctl(CharDriverState *chr, int cmd, void *arg)
         {
             int enable = *(int *)arg;
             if (enable) {
-                tcsendbreak(g_io_channel_unix_get_fd(s->fd_in), 1);
+                tcsendbreak(fioc->fd, 1);
             }
         }
         break;
@@ -1567,7 +1544,7 @@ static int tty_serial_ioctl(CharDriverState *chr, int cmd, void *arg)
         {
             int sarg = 0;
             int *targ = (int *)arg;
-            ioctl(g_io_channel_unix_get_fd(s->fd_in), TIOCMGET, &sarg);
+            ioctl(fioc->fd, TIOCMGET, &sarg);
             *targ = 0;
             if (sarg & TIOCM_CTS)
                 *targ |= CHR_TIOCM_CTS;
@@ -1587,7 +1564,7 @@ static int tty_serial_ioctl(CharDriverState *chr, int cmd, void *arg)
         {
             int sarg = *(int *)arg;
             int targ = 0;
-            ioctl(g_io_channel_unix_get_fd(s->fd_in), TIOCMGET, &targ);
+            ioctl(fioc->fd, TIOCMGET, &targ);
             targ &= ~(CHR_TIOCM_CTS | CHR_TIOCM_CAR | CHR_TIOCM_DSR
                      | CHR_TIOCM_RI | CHR_TIOCM_DTR | CHR_TIOCM_RTS);
             if (sarg & CHR_TIOCM_CTS)
@@ -1602,7 +1579,7 @@ static int tty_serial_ioctl(CharDriverState *chr, int cmd, void *arg)
                 targ |= TIOCM_DTR;
             if (sarg & CHR_TIOCM_RTS)
                 targ |= TIOCM_RTS;
-            ioctl(g_io_channel_unix_get_fd(s->fd_in), TIOCMSET, &targ);
+            ioctl(fioc->fd, TIOCMSET, &targ);
         }
         break;
     default:
@@ -1613,18 +1590,7 @@ static int tty_serial_ioctl(CharDriverState *chr, int cmd, void *arg)
 
 static void qemu_chr_close_tty(CharDriverState *chr)
 {
-    FDCharDriver *s = chr->opaque;
-    int fd = -1;
-
-    if (s) {
-        fd = g_io_channel_unix_get_fd(s->fd_in);
-    }
-
     fd_chr_close(chr);
-
-    if (fd >= 0) {
-        close(fd);
-    }
 }
 
 static CharDriverState *qemu_chr_open_tty_fd(int fd)
@@ -2386,7 +2352,7 @@ static CharDriverState *qemu_chr_open_stdio(ChardevStdio *opts)
 
 typedef struct {
     int fd;
-    GIOChannel *chan;
+    QIOChannel *ioc;
     uint8_t buf[READ_BUF_LEN];
     int bufcnt;
     int bufptr;
@@ -2397,17 +2363,9 @@ typedef struct {
 static int udp_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
 {
     NetCharDriver *s = chr->opaque;
-    gsize bytes_written;
-    GIOStatus status;
-
-    status = g_io_channel_write_chars(s->chan, (const gchar *)buf, len, &bytes_written, NULL);
-    if (status == G_IO_STATUS_EOF) {
-        return 0;
-    } else if (status != G_IO_STATUS_NORMAL) {
-        return -1;
-    }
 
-    return bytes_written;
+    return qio_channel_write(
+        s->ioc, (const char *)buf, len, NULL);
 }
 
 static int udp_chr_read_poll(void *opaque)
@@ -2428,24 +2386,22 @@ static int udp_chr_read_poll(void *opaque)
     return s->max_size;
 }
 
-static gboolean udp_chr_read(GIOChannel *chan, GIOCondition cond, void *opaque)
+static gboolean udp_chr_read(QIOChannel *chan, GIOCondition cond, void *opaque)
 {
     CharDriverState *chr = opaque;
     NetCharDriver *s = chr->opaque;
-    gsize bytes_read = 0;
-    GIOStatus status;
+    ssize_t ret;
 
     if (s->max_size == 0) {
         return TRUE;
     }
-    status = g_io_channel_read_chars(s->chan, (gchar *)s->buf, sizeof(s->buf),
-                                     &bytes_read, NULL);
-    s->bufcnt = bytes_read;
-    s->bufptr = s->bufcnt;
-    if (status != G_IO_STATUS_NORMAL) {
+    ret = qio_channel_read(
+        s->ioc, (char *)s->buf, sizeof(s->buf), NULL);
+    if (ret <= 0) {
         remove_fd_in_watch(chr);
         return FALSE;
     }
+    s->bufcnt = ret;
 
     s->bufptr = 0;
     while (s->max_size > 0 && s->bufptr < s->bufcnt) {
@@ -2462,8 +2418,9 @@ static void udp_chr_update_read_handler(CharDriverState *chr)
     NetCharDriver *s = chr->opaque;
 
     remove_fd_in_watch(chr);
-    if (s->chan) {
-        chr->fd_in_tag = io_add_watch_poll(s->chan, udp_chr_read_poll,
+    if (s->ioc) {
+        chr->fd_in_tag = io_add_watch_poll(s->ioc,
+                                           udp_chr_read_poll,
                                            udp_chr_read, chr);
     }
 }
@@ -2473,9 +2430,8 @@ static void udp_chr_close(CharDriverState *chr)
     NetCharDriver *s = chr->opaque;
 
     remove_fd_in_watch(chr);
-    if (s->chan) {
-        g_io_channel_unref(s->chan);
-        closesocket(s->fd);
+    if (s->ioc) {
+        object_unref(OBJECT(s->ioc));
     }
     g_free(s);
     qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
@@ -2490,7 +2446,11 @@ static CharDriverState *qemu_chr_open_udp_fd(int fd)
     s = g_malloc0(sizeof(NetCharDriver));
 
     s->fd = fd;
-    s->chan = io_channel_from_socket(s->fd);
+    s->ioc = QIO_CHANNEL(qio_channel_socket_new_fd(fd, NULL));
+    if (!s->ioc) {
+        g_free(s);
+        return NULL;
+    }
     s->bufcnt = 0;
     s->bufptr = 0;
     chr->opaque = s;
@@ -2506,19 +2466,18 @@ static CharDriverState *qemu_chr_open_udp_fd(int fd)
 /* TCP Net console */
 
 typedef struct {
-
-    GIOChannel *chan, *listen_chan;
+    QIOChannel *ioc;
+    QIOChannelSocket *listen_ioc;
     guint listen_tag;
-    int fd, listen_fd;
     int connected;
     int max_size;
     int do_telnetopt;
     int do_nodelay;
     int is_unix;
     int *read_msgfds;
-    int read_msgfds_num;
+    size_t read_msgfds_num;
     int *write_msgfds;
-    int write_msgfds_num;
+    size_t write_msgfds_num;
 
     SocketAddress *addr;
     bool is_listen;
@@ -2552,68 +2511,25 @@ static void check_report_connect_error(CharDriverState *chr,
     qemu_chr_socket_restart_timer(chr);
 }
 
-static gboolean tcp_chr_accept(GIOChannel *chan, GIOCondition cond, void *opaque);
-
-#ifndef _WIN32
-static int unix_send_msgfds(CharDriverState *chr, const uint8_t *buf, int len)
-{
-    TCPCharDriver *s = chr->opaque;
-    struct msghdr msgh;
-    struct iovec iov;
-    int r;
-
-    size_t fd_size = s->write_msgfds_num * sizeof(int);
-    char control[CMSG_SPACE(fd_size)];
-    struct cmsghdr *cmsg;
-
-    memset(&msgh, 0, sizeof(msgh));
-    memset(control, 0, sizeof(control));
-
-    /* set the payload */
-    iov.iov_base = (uint8_t *) buf;
-    iov.iov_len = len;
-
-    msgh.msg_iov = &iov;
-    msgh.msg_iovlen = 1;
-
-    msgh.msg_control = control;
-    msgh.msg_controllen = sizeof(control);
-
-    cmsg = CMSG_FIRSTHDR(&msgh);
-
-    cmsg->cmsg_len = CMSG_LEN(fd_size);
-    cmsg->cmsg_level = SOL_SOCKET;
-    cmsg->cmsg_type = SCM_RIGHTS;
-    memcpy(CMSG_DATA(cmsg), s->write_msgfds, fd_size);
-
-    do {
-        r = sendmsg(s->fd, &msgh, 0);
-    } while (r < 0 && errno == EINTR);
-
-    /* free the written msgfds, no matter what */
-    if (s->write_msgfds_num) {
-        g_free(s->write_msgfds);
-        s->write_msgfds = 0;
-        s->write_msgfds_num = 0;
-    }
-
-    return r;
-}
-#endif
+static gboolean tcp_chr_accept(QIOChannel *chan,
+                               GIOCondition cond,
+                               void *opaque);
 
 /* Called with chr_write_lock held.  */
 static int tcp_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
 {
     TCPCharDriver *s = chr->opaque;
     if (s->connected) {
-#ifndef _WIN32
-        if (s->is_unix && s->write_msgfds_num) {
-            return unix_send_msgfds(chr, buf, len);
-        } else
-#endif
-        {
-            return io_channel_send(s->chan, buf, len);
+        int ret =  io_channel_send(s->ioc, buf, len);
+
+        /* free the written msgfds, no matter what */
+        if (s->write_msgfds_num) {
+            g_free(s->write_msgfds);
+            s->write_msgfds = 0;
+            s->write_msgfds_num = 0;
         }
+
+        return ret;
     } else {
         /* XXX: indicate an error ? */
         return len;
@@ -2724,98 +2640,53 @@ static int tcp_set_msgfds(CharDriverState *chr, int *fds, int num)
     return 0;
 }
 
-#ifndef _WIN32
-static void unix_process_msgfd(CharDriverState *chr, struct msghdr *msg)
+static ssize_t tcp_chr_recv(CharDriverState *chr, char *buf, size_t len)
 {
     TCPCharDriver *s = chr->opaque;
-    struct cmsghdr *cmsg;
+    struct iovec iov = { .iov_base = buf, .iov_len = len };
+    int ret;
+    size_t i;
 
-    for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
-        int fd_size, i;
+    /* close and clean read_msgfds */
+    for (i = 0; i < s->read_msgfds_num; i++) {
+        close(s->read_msgfds[i]);
+    }
 
-        if (cmsg->cmsg_len < CMSG_LEN(sizeof(int)) ||
-            cmsg->cmsg_level != SOL_SOCKET ||
-            cmsg->cmsg_type != SCM_RIGHTS) {
-            continue;
-        }
+    if (s->read_msgfds_num) {
+        g_free(s->read_msgfds);
+    }
 
-        fd_size = cmsg->cmsg_len - CMSG_LEN(0);
+    if (qio_channel_has_feature(s->ioc, QIO_CHANNEL_FEATURE_FD_PASS)) {
+        ret = qio_channel_readv_full(s->ioc, &iov, 1,
+                                     &s->read_msgfds, &s->read_msgfds_num,
+                                     0, NULL);
+    } else {
+        ret = qio_channel_readv_full(s->ioc, &iov, 1,
+                                     NULL, NULL,
+                                     0, NULL);
+    }
 
-        if (!fd_size) {
+    for (i = 0; i < s->read_msgfds_num; i++) {
+        int fd = s->read_msgfds[i];
+        if (fd < 0) {
             continue;
         }
 
-        /* close and clean read_msgfds */
-        for (i = 0; i < s->read_msgfds_num; i++) {
-            close(s->read_msgfds[i]);
-        }
-
-        if (s->read_msgfds_num) {
-            g_free(s->read_msgfds);
-        }
-
-        s->read_msgfds_num = fd_size / sizeof(int);
-        s->read_msgfds = g_malloc(fd_size);
-        memcpy(s->read_msgfds, CMSG_DATA(cmsg), fd_size);
-
-        for (i = 0; i < s->read_msgfds_num; i++) {
-            int fd = s->read_msgfds[i];
-            if (fd < 0) {
-                continue;
-            }
-
-            /* O_NONBLOCK is preserved across SCM_RIGHTS so reset it */
-            qemu_set_block(fd);
+        /* O_NONBLOCK is preserved across SCM_RIGHTS so reset it */
+        qemu_set_block(fd);
 
-    #ifndef MSG_CMSG_CLOEXEC
-            qemu_set_cloexec(fd);
-    #endif
-        }
-    }
-}
-
-static ssize_t tcp_chr_recv(CharDriverState *chr, char *buf, size_t len)
-{
-    TCPCharDriver *s = chr->opaque;
-    struct msghdr msg = { NULL, };
-    struct iovec iov[1];
-    union {
-        struct cmsghdr cmsg;
-        char control[CMSG_SPACE(sizeof(int) * TCP_MAX_FDS)];
-    } msg_control;
-    int flags = 0;
-    ssize_t ret;
-
-    iov[0].iov_base = buf;
-    iov[0].iov_len = len;
-
-    msg.msg_iov = iov;
-    msg.msg_iovlen = 1;
-    msg.msg_control = &msg_control;
-    msg.msg_controllen = sizeof(msg_control);
-
-#ifdef MSG_CMSG_CLOEXEC
-    flags |= MSG_CMSG_CLOEXEC;
+#ifndef MSG_CMSG_CLOEXEC
+        qemu_set_cloexec(fd);
 #endif
-    ret = recvmsg(s->fd, &msg, flags);
-    if (ret > 0 && s->is_unix) {
-        unix_process_msgfd(chr, &msg);
     }
 
     return ret;
 }
-#else
-static ssize_t tcp_chr_recv(CharDriverState *chr, char *buf, size_t len)
-{
-    TCPCharDriver *s = chr->opaque;
-    return qemu_recv(s->fd, buf, len, 0);
-}
-#endif
 
 static GSource *tcp_chr_add_watch(CharDriverState *chr, GIOCondition cond)
 {
     TCPCharDriver *s = chr->opaque;
-    return g_io_create_watch(s->chan, cond);
+    return qio_channel_create_watch(s->ioc, cond);
 }
 
 static void tcp_chr_disconnect(CharDriverState *chr)
@@ -2823,15 +2694,13 @@ static void tcp_chr_disconnect(CharDriverState *chr)
     TCPCharDriver *s = chr->opaque;
 
     s->connected = 0;
-    if (s->listen_chan) {
-        s->listen_tag = g_io_add_watch(s->listen_chan, G_IO_IN,
-                                       tcp_chr_accept, chr);
+    if (s->listen_ioc) {
+        s->listen_tag = qio_channel_add_watch(
+            QIO_CHANNEL(s->listen_ioc), G_IO_IN, tcp_chr_accept, chr);
     }
     remove_fd_in_watch(chr);
-    g_io_channel_unref(s->chan);
-    s->chan = NULL;
-    closesocket(s->fd);
-    s->fd = -1;
+    object_unref(OBJECT(s->ioc));
+    s->ioc = NULL;
     SocketAddress_to_str(chr->filename, CHR_MAX_FILENAME_SIZE,
                          "disconnected:", s->addr, s->is_listen, s->is_telnet);
     qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
@@ -2840,7 +2709,7 @@ static void tcp_chr_disconnect(CharDriverState *chr)
     }
 }
 
-static gboolean tcp_chr_read(GIOChannel *chan, GIOCondition cond, void *opaque)
+static gboolean tcp_chr_read(QIOChannel *chan, GIOCondition cond, void *opaque)
 {
     CharDriverState *chr = opaque;
     TCPCharDriver *s = chr->opaque;
@@ -2860,7 +2729,7 @@ static gboolean tcp_chr_read(GIOChannel *chan, GIOCondition cond, void *opaque)
     if (len > s->max_size)
         len = s->max_size;
     size = tcp_chr_recv(chr, (void *)buf, len);
-    if (size == 0) {
+    if (size == 0 || size == -1) {
         /* connection closed */
         tcp_chr_disconnect(chr);
     } else if (size > 0) {
@@ -2908,25 +2777,17 @@ static void tcp_chr_connect(void *opaque)
 {
     CharDriverState *chr = opaque;
     TCPCharDriver *s = chr->opaque;
-    struct sockaddr_storage ss, ps;
-    socklen_t ss_len = sizeof(ss), ps_len = sizeof(ps);
-
-    memset(&ss, 0, ss_len);
-    if (getsockname(s->fd, (struct sockaddr *) &ss, &ss_len) != 0) {
-        snprintf(chr->filename, CHR_MAX_FILENAME_SIZE,
-                 "Error in getsockname: %s\n", strerror(errno));
-    } else if (getpeername(s->fd, (struct sockaddr *) &ps, &ps_len) != 0) {
-        snprintf(chr->filename, CHR_MAX_FILENAME_SIZE,
-                 "Error in getpeername: %s\n", strerror(errno));
-    } else {
-        sockaddr_to_str(chr->filename, CHR_MAX_FILENAME_SIZE,
-                        &ss, ss_len, &ps, ps_len,
-                        s->is_listen, s->is_telnet);
-    }
+    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(s->ioc);
+
+    sockaddr_to_str(chr->filename, CHR_MAX_FILENAME_SIZE,
+                    &sioc->localAddr, sioc->localAddrLen,
+                    &sioc->remoteAddr, sioc->remoteAddrLen,
+                    s->is_listen, s->is_telnet);
 
     s->connected = 1;
-    if (s->chan) {
-        chr->fd_in_tag = io_add_watch_poll(s->chan, tcp_chr_read_poll,
+    if (s->ioc) {
+        chr->fd_in_tag = io_add_watch_poll(s->ioc,
+                                           tcp_chr_read_poll,
                                            tcp_chr_read, chr);
     }
     qemu_chr_be_generic_open(chr);
@@ -2937,38 +2798,42 @@ static void tcp_chr_update_read_handler(CharDriverState *chr)
     TCPCharDriver *s = chr->opaque;
 
     remove_fd_in_watch(chr);
-    if (s->chan) {
-        chr->fd_in_tag = io_add_watch_poll(s->chan, tcp_chr_read_poll,
+    if (s->ioc) {
+        chr->fd_in_tag = io_add_watch_poll(s->ioc,
+                                           tcp_chr_read_poll,
                                            tcp_chr_read, chr);
     }
 }
 
 #define IACSET(x,a,b,c) x[0] = a; x[1] = b; x[2] = c;
-static void tcp_chr_telnet_init(int fd)
+static void tcp_chr_telnet_init(QIOChannel *ioc)
 {
     char buf[3];
     /* Send the telnet negotion to put telnet in binary, no echo, single char mode */
     IACSET(buf, 0xff, 0xfb, 0x01);  /* IAC WILL ECHO */
-    send(fd, (char *)buf, 3, 0);
+    qio_channel_write(ioc, buf, 3, NULL);
     IACSET(buf, 0xff, 0xfb, 0x03);  /* IAC WILL Suppress go ahead */
-    send(fd, (char *)buf, 3, 0);
+    qio_channel_write(ioc, buf, 3, NULL);
     IACSET(buf, 0xff, 0xfb, 0x00);  /* IAC WILL Binary */
-    send(fd, (char *)buf, 3, 0);
+    qio_channel_write(ioc, buf, 3, NULL);
     IACSET(buf, 0xff, 0xfd, 0x00);  /* IAC DO Binary */
-    send(fd, (char *)buf, 3, 0);
+    qio_channel_write(ioc, buf, 3, NULL);
 }
 
-static int tcp_chr_add_client(CharDriverState *chr, int fd)
+static int tcp_chr_new_client(CharDriverState *chr, QIOChannel *ioc)
 {
     TCPCharDriver *s = chr->opaque;
-    if (s->fd != -1)
+    if (s->ioc != NULL) {
 	return -1;
+    }
 
-    qemu_set_nonblock(fd);
-    if (s->do_nodelay)
-        socket_set_nodelay(fd);
-    s->fd = fd;
-    s->chan = io_channel_from_socket(fd);
+    s->ioc = ioc;
+    object_ref(OBJECT(ioc));
+
+    qio_channel_set_blocking(s->ioc, false);
+    if (s->do_nodelay) {
+        qio_channel_socket_set_nodelay(QIO_CHANNEL_SOCKET(s->ioc), true);
+    }
     if (s->listen_tag) {
         g_source_remove(s->listen_tag);
         s->listen_tag = 0;
@@ -2978,41 +2843,42 @@ static int tcp_chr_add_client(CharDriverState *chr, int fd)
     return 0;
 }
 
-static gboolean tcp_chr_accept(GIOChannel *channel, GIOCondition cond, void *opaque)
+
+static int tcp_chr_add_client(CharDriverState *chr, int fd)
+{
+    int ret;
+    QIOChannel *ioc;
+
+    ioc = QIO_CHANNEL(qio_channel_socket_new_fd(fd, NULL));
+    if (!ioc) {
+        return -1;
+    }
+    ret = tcp_chr_new_client(chr, ioc);
+    object_unref(OBJECT(ioc));
+    return ret;
+}
+
+static gboolean tcp_chr_accept(QIOChannel *channel,
+                               GIOCondition cond,
+                               void *opaque)
 {
     CharDriverState *chr = opaque;
     TCPCharDriver *s = chr->opaque;
-    struct sockaddr_in saddr;
-#ifndef _WIN32
-    struct sockaddr_un uaddr;
-#endif
-    struct sockaddr *addr;
-    socklen_t len;
-    int fd;
+    QIOChannel *ioc;
 
-    for(;;) {
-#ifndef _WIN32
-	if (s->is_unix) {
-	    len = sizeof(uaddr);
-	    addr = (struct sockaddr *)&uaddr;
-	} else
-#endif
-	{
-	    len = sizeof(saddr);
-	    addr = (struct sockaddr *)&saddr;
-	}
-        fd = qemu_accept(s->listen_fd, addr, &len);
-        if (fd < 0 && errno != EINTR) {
-            s->listen_tag = 0;
-            return FALSE;
-        } else if (fd >= 0) {
-            if (s->do_telnetopt)
-                tcp_chr_telnet_init(fd);
-            break;
-        }
+    ioc = QIO_CHANNEL(
+        qio_channel_socket_accept(QIO_CHANNEL_SOCKET(channel), NULL));
+    if (!ioc) {
+        return TRUE;
+    }
+
+    if (s->do_telnetopt) {
+        tcp_chr_telnet_init(ioc);
     }
-    if (tcp_chr_add_client(chr, fd) < 0)
-	close(fd);
+
+    tcp_chr_new_client(chr, ioc);
+
+    object_unref(OBJECT(ioc));
 
     return TRUE;
 }
@@ -3027,22 +2893,16 @@ static void tcp_chr_close(CharDriverState *chr)
         s->reconnect_timer = 0;
     }
     qapi_free_SocketAddress(s->addr);
-    if (s->fd >= 0) {
-        remove_fd_in_watch(chr);
-        if (s->chan) {
-            g_io_channel_unref(s->chan);
-        }
-        closesocket(s->fd);
+    remove_fd_in_watch(chr);
+    if (s->ioc) {
+        object_unref(OBJECT(s->ioc));
     }
-    if (s->listen_fd >= 0) {
-        if (s->listen_tag) {
-            g_source_remove(s->listen_tag);
-            s->listen_tag = 0;
-        }
-        if (s->listen_chan) {
-            g_io_channel_unref(s->listen_chan);
-        }
-        closesocket(s->listen_fd);
+    if (s->listen_tag) {
+        g_source_remove(s->listen_tag);
+        s->listen_tag = 0;
+    }
+    if (s->listen_ioc) {
+        object_unref(OBJECT(s->listen_ioc));
     }
     if (s->read_msgfds_num) {
         for (i = 0; i < s->read_msgfds_num; i++) {
@@ -3062,16 +2922,21 @@ static void qemu_chr_finish_socket_connection(CharDriverState *chr, int fd)
     TCPCharDriver *s = chr->opaque;
 
     if (s->is_listen) {
-        s->listen_fd = fd;
-        s->listen_chan = io_channel_from_socket(s->listen_fd);
-        s->listen_tag = g_io_add_watch(s->listen_chan, G_IO_IN,
-                                       tcp_chr_accept, chr);
+        s->listen_ioc = qio_channel_socket_new_fd(fd, NULL);
+        if (!s->listen_ioc) {
+            close(fd);
+            return;
+        }
+        s->listen_tag = qio_channel_add_watch(
+            QIO_CHANNEL(s->listen_ioc), G_IO_IN, tcp_chr_accept, chr);
     } else {
-        s->connected = 1;
-        s->fd = fd;
-        socket_set_nodelay(fd);
-        s->chan = io_channel_from_socket(s->fd);
-        tcp_chr_connect(chr);
+        QIOChannel *ioc = QIO_CHANNEL(qio_channel_socket_new_fd(fd, NULL));
+        if (!ioc) {
+            close(fd);
+            return;
+        }
+        tcp_chr_new_client(chr, ioc);
+        object_unref(OBJECT(ioc));
     }
 }
 
@@ -4143,8 +4008,6 @@ static CharDriverState *qmp_chardev_open_socket(ChardevSocket *sock,
     chr = qemu_chr_alloc();
     s = g_malloc0(sizeof(TCPCharDriver));
 
-    s->fd = -1;
-    s->listen_fd = -1;
     s->is_unix = addr->kind == SOCKET_ADDRESS_KIND_UNIX;
     s->is_listen = is_listen;
     s->is_telnet = is_telnet;
@@ -4187,8 +4050,8 @@ static CharDriverState *qmp_chardev_open_socket(ChardevSocket *sock,
     if (is_listen && is_waitconnect) {
         fprintf(stderr, "QEMU waiting for connection on: %s\n",
                 chr->filename);
-        tcp_chr_accept(s->listen_chan, G_IO_IN, chr);
-        qemu_set_nonblock(s->listen_fd);
+        tcp_chr_accept(QIO_CHANNEL(s->listen_ioc), G_IO_IN, chr);
+        qio_channel_set_blocking(QIO_CHANNEL(s->listen_ioc), false);
     }
 
     return chr;
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 33/34] char: don't assume telnet initialization will not block
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (31 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 32/34] char: convert from GIOChannel to QIOChannel Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 34/34] char: introduce support for TLS encrypted TCP chardev backend Daniel P. Berrange
  2015-04-23 12:28 ` [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Stefan Hajnoczi
  34 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

The current code for doing telnet initialization is writing to
a socket without checking the return status. While it is highly
unlikely to be a problem when writing to a bare socket, as the
buffers are large enough to prevent blocking, this cannot be
assumed safe with TLS sockets. So write the telnet initialization
code into a memory buffer and then use an I/O watch to fully
send the data.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 qemu-char.c | 85 ++++++++++++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 67 insertions(+), 18 deletions(-)

diff --git a/qemu-char.c b/qemu-char.c
index fada1a7..85fbbaf 100644
--- a/qemu-char.c
+++ b/qemu-char.c
@@ -2805,19 +2805,68 @@ static void tcp_chr_update_read_handler(CharDriverState *chr)
     }
 }
 
-#define IACSET(x,a,b,c) x[0] = a; x[1] = b; x[2] = c;
-static void tcp_chr_telnet_init(QIOChannel *ioc)
+typedef struct {
+    CharDriverState *chr;
+    char buf[12];
+    size_t buflen;
+} TCPCharDriverTelnetInit;
+
+static gboolean tcp_chr_telnet_init_io(QIOChannel *ioc,
+                                       GIOCondition cond G_GNUC_UNUSED,
+                                       gpointer user_data)
 {
-    char buf[3];
-    /* Send the telnet negotion to put telnet in binary, no echo, single char mode */
-    IACSET(buf, 0xff, 0xfb, 0x01);  /* IAC WILL ECHO */
-    qio_channel_write(ioc, buf, 3, NULL);
-    IACSET(buf, 0xff, 0xfb, 0x03);  /* IAC WILL Suppress go ahead */
-    qio_channel_write(ioc, buf, 3, NULL);
-    IACSET(buf, 0xff, 0xfb, 0x00);  /* IAC WILL Binary */
-    qio_channel_write(ioc, buf, 3, NULL);
-    IACSET(buf, 0xff, 0xfd, 0x00);  /* IAC DO Binary */
-    qio_channel_write(ioc, buf, 3, NULL);
+    TCPCharDriverTelnetInit *init = user_data;
+    ssize_t ret;
+
+    ret = qio_channel_write(ioc, init->buf, init->buflen, NULL);
+    if (ret < 0) {
+        if (ret == QIO_CHANNEL_ERR_BLOCK) {
+            ret = 0;
+        } else {
+            tcp_chr_disconnect(init->chr);
+            return FALSE;
+        }
+    }
+    init->buflen -= ret;
+
+    if (init->buflen == 0) {
+        tcp_chr_connect(init->chr);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static void tcp_chr_telnet_init(CharDriverState *chr)
+{
+    TCPCharDriver *s = chr->opaque;
+    TCPCharDriverTelnetInit *init =
+        g_new0(TCPCharDriverTelnetInit, 1);
+    size_t n = 0;
+
+    init->chr = chr;
+    init->buflen = 12;
+
+#define IACSET(x, a, b, c)                      \
+    do {                                        \
+        x[n++] = a;                             \
+        x[n++] = b;                             \
+        x[n++] = c;                             \
+    } while (0)
+
+    /* Prep the telnet negotion to put telnet in binary,
+     * no echo, single char mode */
+    IACSET(init->buf, 0xff, 0xfb, 0x01);  /* IAC WILL ECHO */
+    IACSET(init->buf, 0xff, 0xfb, 0x03);  /* IAC WILL Suppress go ahead */
+    IACSET(init->buf, 0xff, 0xfb, 0x00);  /* IAC WILL Binary */
+    IACSET(init->buf, 0xff, 0xfd, 0x00);  /* IAC DO Binary */
+
+#undef IACSET
+
+    qio_channel_add_watch(
+        s->ioc, G_IO_OUT,
+        tcp_chr_telnet_init_io,
+        init);
 }
 
 static int tcp_chr_new_client(CharDriverState *chr, QIOChannel *ioc)
@@ -2838,7 +2887,12 @@ static int tcp_chr_new_client(CharDriverState *chr, QIOChannel *ioc)
         g_source_remove(s->listen_tag);
         s->listen_tag = 0;
     }
-    tcp_chr_connect(chr);
+
+    if (s->do_telnetopt) {
+        tcp_chr_telnet_init(chr);
+    } else {
+        tcp_chr_connect(chr);
+    }
 
     return 0;
 }
@@ -2863,7 +2917,6 @@ static gboolean tcp_chr_accept(QIOChannel *channel,
                                void *opaque)
 {
     CharDriverState *chr = opaque;
-    TCPCharDriver *s = chr->opaque;
     QIOChannel *ioc;
 
     ioc = QIO_CHANNEL(
@@ -2872,10 +2925,6 @@ static gboolean tcp_chr_accept(QIOChannel *channel,
         return TRUE;
     }
 
-    if (s->do_telnetopt) {
-        tcp_chr_telnet_init(ioc);
-    }
-
     tcp_chr_new_client(chr, ioc);
 
     object_unref(OBJECT(ioc));
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* [Qemu-devel] [PATCH v1 RFC 34/34] char: introduce support for TLS encrypted TCP chardev backend
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (32 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 33/34] char: don't assume telnet initialization will not block Daniel P. Berrange
@ 2015-04-17 14:22 ` Daniel P. Berrange
  2015-04-17 18:27   ` Eric Blake
  2015-05-04 20:07   ` Kashyap Chamarthy
  2015-04-23 12:28 ` [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Stefan Hajnoczi
  34 siblings, 2 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 14:22 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

This integrates support for QIOChannelTLS object in the TCP
chardev backend. If the 'tls-cred=NAME' option is passed with
the '-chardev tcp' argument, then it will setup the chardev
such that the client is required to establish a TLS handshake
when connecting. The 'acl' option will further enable the
creation of a 'char.$ID.tlspeername' ACL which will be used
to validate the client x509 certificate, if provided.

A complete invokation to run QEMU as the server for a TLS
encrypted serial dev might be

  $ qemu-system-x86_64 \
      -nodefconfig -nodefaults -device sga -display none \
      -chardev socket,id=s0,host=127.0.0.1,port=9000,tls-cred=tls0,server \
      -device isa-serial,chardev=s0 \
      -object qcrypto-tls-cred,id=tls0,credtype=x509,\
        endpoint=server,dir=/home/berrange/security/qemutls,verify-peer=off

To test with the gnutls-cli tool as the client:

  $ gnutls-cli --priority=NORMAL -p 9000 \
       --x509cafile=/home/berrange/security/qemutls/ca-cert.pem \
       127.0.0.1

If QEMU was told to use 'anon' credential type, then use the
priority string 'NOMAL:+ANON-DH' with gnutls-cli

Alternatively, if setting up a chardev to operate as a client,
then the TLS credentials registered must be for the client
endpoint. First a TLS server must be setup, which can be done
with the gnutls-serv tool

  $ gnutls-serv --priority=NORMAL -p 9000 \
       --x509cafile=/home/berrange/security/qemutls/ca-cert.pem \
       --x509certfile=/home/berrange/security/qemutls/server-cert.pem \
       --x509keyfile=/home/berrange/security/qemutls/server-key.pem

Then QEMU can connect with

  $ qemu-system-x86_64 \
      -nodefconfig -nodefaults -device sga -display none \
      -chardev socket,id=s0,host=127.0.0.1,port=9000,tls-cred=tls0 \
      -device isa-serial,chardev=s0 \
      -object qcrypto-tls-cred,id=tls0,credtype=x509,\
        endpoint=client,dir=/home/berrange/security/qemutls

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 qapi-schema.json |   2 +
 qemu-char.c      | 182 ++++++++++++++++++++++++++++++++++++++++++++++---------
 qemu-options.hx  |   9 ++-
 3 files changed, 161 insertions(+), 32 deletions(-)

diff --git a/qapi-schema.json b/qapi-schema.json
index ac9594d..062a455 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -2782,6 +2782,8 @@
 # Since: 1.4
 ##
 { 'type': 'ChardevSocket', 'data': { 'addr'       : 'SocketAddress',
+                                     '*tls-cred'  : 'str',
+                                     '*acl'       : 'str',
                                      '*server'    : 'bool',
                                      '*wait'      : 'bool',
                                      '*nodelay'   : 'bool',
diff --git a/qemu-char.c b/qemu-char.c
index 85fbbaf..da6f188 100644
--- a/qemu-char.c
+++ b/qemu-char.c
@@ -33,6 +33,8 @@
 #include "qapi-visit.h"
 #include "io/channel-socket.h"
 #include "io/channel-file.h"
+#include "io/channel-tls.h"
+#include "qemu/acl.h"
 
 #include <unistd.h>
 #include <fcntl.h>
@@ -2466,9 +2468,12 @@ static CharDriverState *qemu_chr_open_udp_fd(int fd)
 /* TCP Net console */
 
 typedef struct {
-    QIOChannel *ioc;
+    QIOChannel *ioc; /* Client I/O channel */
+    QIOChannelSocket *sioc; /* Client master channel */
     QIOChannelSocket *listen_ioc;
     guint listen_tag;
+    QCryptoTLSCreds *tls_creds;
+    char *acl;
     int connected;
     int max_size;
     int do_telnetopt;
@@ -2699,6 +2704,8 @@ static void tcp_chr_disconnect(CharDriverState *chr)
             QIO_CHANNEL(s->listen_ioc), G_IO_IN, tcp_chr_accept, chr);
     }
     remove_fd_in_watch(chr);
+    object_unref(OBJECT(s->sioc));
+    s->sioc = NULL;
     object_unref(OBJECT(s->ioc));
     s->ioc = NULL;
     SocketAddress_to_str(chr->filename, CHR_MAX_FILENAME_SIZE,
@@ -2777,11 +2784,10 @@ static void tcp_chr_connect(void *opaque)
 {
     CharDriverState *chr = opaque;
     TCPCharDriver *s = chr->opaque;
-    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(s->ioc);
 
     sockaddr_to_str(chr->filename, CHR_MAX_FILENAME_SIZE,
-                    &sioc->localAddr, sioc->localAddrLen,
-                    &sioc->remoteAddr, sioc->remoteAddrLen,
+                    &s->sioc->localAddr, s->sioc->localAddrLen,
+                    &s->sioc->remoteAddr, s->sioc->remoteAddrLen,
                     s->is_listen, s->is_telnet);
 
     s->connected = 1;
@@ -2869,29 +2875,85 @@ static void tcp_chr_telnet_init(CharDriverState *chr)
         init);
 }
 
-static int tcp_chr_new_client(CharDriverState *chr, QIOChannel *ioc)
+
+static void tcp_chr_tls_handshake(QIOTask *task,
+                                  gpointer user_data)
+{
+    CharDriverState *chr = user_data;
+    TCPCharDriver *s = chr->opaque;
+
+    if (task->err) {
+        tcp_chr_disconnect(chr);
+    } else {
+        if (s->do_telnetopt) {
+            tcp_chr_telnet_init(chr);
+        } else {
+            tcp_chr_connect(chr);
+        }
+    }
+}
+
+
+static void tcp_chr_tls_init(CharDriverState *chr)
+{
+    TCPCharDriver *s = chr->opaque;
+    QIOChannelTLS *tioc;
+    Error *err = NULL;
+
+    if (s->is_listen) {
+        tioc = qio_channel_tls_new_server(
+            s->ioc, s->tls_creds,
+            s->acl,
+            &err);
+    } else {
+        tioc = qio_channel_tls_new_client(
+            s->ioc, s->tls_creds,
+            s->addr->inet->host,
+            &err);
+    }
+    if (tioc == NULL) {
+        error_free(err);
+        tcp_chr_disconnect(chr);
+    }
+    object_unref(OBJECT(s->ioc));
+    s->ioc = QIO_CHANNEL(tioc);
+
+    qio_channel_tls_handshake(tioc,
+                              tcp_chr_tls_handshake,
+                              chr,
+                              NULL);
+}
+
+
+static int tcp_chr_new_client(CharDriverState *chr, QIOChannelSocket *sioc)
 {
     TCPCharDriver *s = chr->opaque;
     if (s->ioc != NULL) {
 	return -1;
     }
 
-    s->ioc = ioc;
-    object_ref(OBJECT(ioc));
+    s->ioc = QIO_CHANNEL(sioc);
+    object_ref(OBJECT(s->ioc));
+    s->sioc = sioc;
+    object_ref(OBJECT(s->sioc));
 
     qio_channel_set_blocking(s->ioc, false);
     if (s->do_nodelay) {
-        qio_channel_socket_set_nodelay(QIO_CHANNEL_SOCKET(s->ioc), true);
+        qio_channel_socket_set_nodelay(s->sioc, true);
     }
     if (s->listen_tag) {
         g_source_remove(s->listen_tag);
         s->listen_tag = 0;
     }
 
-    if (s->do_telnetopt) {
-        tcp_chr_telnet_init(chr);
+    if (s->tls_creds) {
+        tcp_chr_tls_init(chr);
     } else {
-        tcp_chr_connect(chr);
+        if (s->do_telnetopt) {
+            tcp_chr_telnet_init(chr);
+        } else {
+            tcp_chr_connect(chr);
+        }
     }
 
     return 0;
@@ -2901,14 +2963,14 @@ static int tcp_chr_new_client(CharDriverState *chr, QIOChannel *ioc)
 static int tcp_chr_add_client(CharDriverState *chr, int fd)
 {
     int ret;
-    QIOChannel *ioc;
+    QIOChannelSocket *sioc;
 
-    ioc = QIO_CHANNEL(qio_channel_socket_new_fd(fd, NULL));
-    if (!ioc) {
+    sioc = qio_channel_socket_new_fd(fd, NULL);
+    if (!sioc) {
         return -1;
     }
-    ret = tcp_chr_new_client(chr, ioc);
-    object_unref(OBJECT(ioc));
+    ret = tcp_chr_new_client(chr, sioc);
+    object_unref(OBJECT(sioc));
     return ret;
 }
 
@@ -2917,17 +2979,16 @@ static gboolean tcp_chr_accept(QIOChannel *channel,
                                void *opaque)
 {
     CharDriverState *chr = opaque;
-    QIOChannel *ioc;
+    QIOChannelSocket *sioc;
 
-    ioc = QIO_CHANNEL(
-        qio_channel_socket_accept(QIO_CHANNEL_SOCKET(channel), NULL));
-    if (!ioc) {
+    sioc = qio_channel_socket_accept(QIO_CHANNEL_SOCKET(channel), NULL);
+    if (!sioc) {
         return TRUE;
     }
 
-    tcp_chr_new_client(chr, ioc);
+    tcp_chr_new_client(chr, sioc);
 
-    object_unref(OBJECT(ioc));
+    object_unref(OBJECT(sioc));
 
     return TRUE;
 }
@@ -2959,6 +3020,9 @@ static void tcp_chr_close(CharDriverState *chr)
         }
         g_free(s->read_msgfds);
     }
+    if (s->tls_creds) {
+        object_unref(OBJECT(s->tls_creds));
+    }
     if (s->write_msgfds_num) {
         g_free(s->write_msgfds);
     }
@@ -2979,13 +3043,13 @@ static void qemu_chr_finish_socket_connection(CharDriverState *chr, int fd)
         s->listen_tag = qio_channel_add_watch(
             QIO_CHANNEL(s->listen_ioc), G_IO_IN, tcp_chr_accept, chr);
     } else {
-        QIOChannel *ioc = QIO_CHANNEL(qio_channel_socket_new_fd(fd, NULL));
-        if (!ioc) {
+        QIOChannelSocket *sioc = qio_channel_socket_new_fd(fd, NULL);
+        if (!sioc) {
             close(fd);
             return;
         }
-        tcp_chr_new_client(chr, ioc);
-        object_unref(OBJECT(ioc));
+        tcp_chr_new_client(chr, sioc);
+        object_unref(OBJECT(sioc));
     }
 }
 
@@ -3450,6 +3514,8 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
     const char *path = qemu_opt_get(opts, "path");
     const char *host = qemu_opt_get(opts, "host");
     const char *port = qemu_opt_get(opts, "port");
+    const char *tls_cred = qemu_opt_get(opts, "tls-cred");
+    bool acl = qemu_opt_get_bool(opts, "acl", false);
     SocketAddress *addr;
 
     if (!path) {
@@ -3461,6 +3527,11 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
             error_setg(errp, "chardev: socket: no port given");
             return;
         }
+    } else {
+        if (tls_cred) {
+            error_setg(errp, "TLS can only be used over TCP socket");
+            return;
+        }
     }
 
     backend->socket = g_new0(ChardevSocket, 1);
@@ -3475,6 +3546,11 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
     backend->socket->wait = is_waitconnect;
     backend->socket->has_reconnect = true;
     backend->socket->reconnect = reconnect;
+    backend->socket->tls_cred = g_strdup(tls_cred);
+    if (acl) {
+        backend->socket->acl = g_strdup_printf("chr.%s.tlspeername",
+                                               qemu_opts_id(opts));
+    }
 
     addr = g_new0(SocketAddress, 1);
     if (path) {
@@ -3494,6 +3570,7 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
         addr->inet->ipv6 = qemu_opt_get_bool(opts, "ipv6", 0);
     }
     backend->socket->addr = addr;
+    return;
 }
 
 static void qemu_chr_parse_udp(QemuOpts *opts, ChardevBackend *backend,
@@ -3877,6 +3954,9 @@ QemuOptsList qemu_chardev_opts = {
             .name = "telnet",
             .type = QEMU_OPT_BOOL,
         },{
+            .name = "tls-cred",
+            .type = QEMU_OPT_STRING,
+        },{
             .name = "width",
             .type = QEMU_OPT_NUMBER,
         },{
@@ -4061,6 +4141,41 @@ static CharDriverState *qmp_chardev_open_socket(ChardevSocket *sock,
     s->is_listen = is_listen;
     s->is_telnet = is_telnet;
     s->do_nodelay = do_nodelay;
+    if (sock->tls_cred) {
+        Object *container;
+        container = container_get(object_get_root(), "/objects");
+        /* XXX this type cast causes an abort if the type is wrong.
+         * This is bad. We should check and set an error */
+        s->tls_creds = QCRYPTO_TLS_CREDS(
+            object_resolve_path_component(container,
+                                          sock->tls_cred));
+        if (!s->tls_creds) {
+            error_setg(errp, "No TLS credentials with id '%s'",
+                       sock->tls_cred);
+            goto error;
+        }
+        object_ref(OBJECT(s->tls_creds));
+        if (is_listen) {
+            if (s->tls_creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
+                error_setg(errp, "%s",
+                           "Expected TLS credentials for server endpoint");
+                goto error;
+            }
+        } else {
+            if (s->tls_creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) {
+                error_setg(errp, "%s",
+                           "Expected TLS credentials for client endpoint");
+                goto error;
+            }
+        }
+
+        if (sock->acl) {
+            s->acl = g_strdup(sock->acl);
+
+            qemu_acl_init(s->acl);
+        }
+    }
+
     qapi_copy_SocketAddress(&s->addr, sock->addr);
 
     chr->opaque = s;
@@ -4090,10 +4205,7 @@ static CharDriverState *qmp_chardev_open_socket(ChardevSocket *sock,
     if (s->reconnect_time) {
         socket_try_connect(chr);
     } else if (!qemu_chr_open_socket_fd(chr, errp)) {
-        g_free(s);
-        g_free(chr->filename);
-        g_free(chr);
-        return NULL;
+        goto error;
     }
 
     if (is_listen && is_waitconnect) {
@@ -4104,6 +4216,16 @@ static CharDriverState *qmp_chardev_open_socket(ChardevSocket *sock,
     }
 
     return chr;
+
+ error:
+    if (s->tls_creds) {
+        object_unref(OBJECT(s->tls_creds));
+    }
+    g_free(s->acl);
+    g_free(s);
+    g_free(chr->filename);
+    g_free(chr);
+    return NULL;
 }
 
 static CharDriverState *qmp_chardev_open_udp(ChardevUdp *udp,
diff --git a/qemu-options.hx b/qemu-options.hx
index 44d9be2..0c764fc 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -2009,7 +2009,7 @@ ETEXI
 DEF("chardev", HAS_ARG, QEMU_OPTION_chardev,
     "-chardev null,id=id[,mux=on|off]\n"
     "-chardev socket,id=id[,host=host],port=port[,to=to][,ipv4][,ipv6][,nodelay][,reconnect=seconds]\n"
-    "         [,server][,nowait][,telnet][,reconnect=seconds][,mux=on|off] (tcp)\n"
+    "         [,server][,nowait][,telnet][,reconnect=seconds][,mux=on|off][,tls-cred=ID][,acl] (tcp)\n"
     "-chardev socket,id=id,path=path[,server][,nowait][,telnet][,reconnect=seconds][,mux=on|off] (unix)\n"
     "-chardev udp,id=id[,host=host],port=port[,localaddr=localaddr]\n"
     "         [,localport=localport][,ipv4][,ipv6][,mux=on|off]\n"
@@ -2082,7 +2082,7 @@ Options to each backend are described below.
 A void device. This device will not emit any data, and will drop any data it
 receives. The null backend does not take any options.
 
-@item -chardev socket ,id=@var{id} [@var{TCP options} or @var{unix options}] [,server] [,nowait] [,telnet] [,reconnect=@var{seconds}]
+@item -chardev socket ,id=@var{id} [@var{TCP options} or @var{unix options}] [,server] [,nowait] [,telnet] [,reconnect=@var{seconds}][,tls-cred=@var{id}]
 
 Create a two-way stream socket, which can be either a TCP or a unix socket. A
 unix socket will be created if @option{path} is specified. Behaviour is
@@ -2100,6 +2100,11 @@ escape sequences.
 the remote end goes away.  qemu will delay this many seconds and then attempt
 to reconnect.  Zero disables reconnecting, and is the default.
 
+@option{tls-cred} requests enablement of the TLS protocol for encryption,
+and specifies the id of the TLS credentials to use for the handshake. The
+credentials must be previously created with the @option{-object qcrypto-tls-cred}
+argument.
+
 TCP and unix socket options are given below:
 
 @table @option
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 04/34] qom: add object_new_propv / object_new_proplist constructors
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 04/34] qom: add object_new_propv / object_new_proplist constructors Daniel P. Berrange
@ 2015-04-17 14:55   ` Paolo Bonzini
  2015-04-17 15:16     ` Daniel P. Berrange
  2015-04-17 16:11   ` Eric Blake
  1 sibling, 1 reply; 71+ messages in thread
From: Paolo Bonzini @ 2015-04-17 14:55 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Gerd Hoffmann, Stefan Hajnoczi



On 17/04/2015 16:22, Daniel P. Berrange wrote:
> +
> +Object *object_new_proplist(const char *typename,
> +                            const char *id,
> +                            Error **errp,
> +                            va_list vargs)
> +{
> +    Object *obj;
> +    const char *propname;
> +
> +    obj = object_new(typename);
> +
> +    if (object_class_is_abstract(object_get_class(obj))) {
> +        error_setg(errp, "object type '%s' is abstract", typename);
> +        goto error;
> +    }
> +
> +    propname = va_arg(vargs, char *);
> +    while (propname != NULL) {
> +        const char *value = va_arg(vargs, char *);
> +
> +        object_property_parse(obj, value, propname, errp);
> +        if (*errp) {
> +            goto error;
> +        }
> +        propname = va_arg(vargs, char *);
> +    }
> +
> +    object_property_add_child(container_get(object_get_root(), "/objects"),
> +                              id, obj, errp);

I would pass the parent as an additional argument to
object_new_proplist().  Otherwise looks good.

Paolo

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 05/34] qom: make enum string tables const-correct
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 05/34] qom: make enum string tables const-correct Daniel P. Berrange
@ 2015-04-17 14:56   ` Paolo Bonzini
  0 siblings, 0 replies; 71+ messages in thread
From: Paolo Bonzini @ 2015-04-17 14:56 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Gerd Hoffmann, Stefan Hajnoczi



On 17/04/2015 16:22, Daniel P. Berrange wrote:
> The enum string table parameters in various QOM/QAPI methods
> are declared 'const char *strings[]'. This results in const
> warnings if passed a variable that was declared as
> 
>    static const char * const strings[] = { .... };
> 
> Add the extra const annotation to the parameters, since
> neither the string elements, nor the array itself should
> ever be modified.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  include/hw/qdev-core.h      | 2 +-
>  include/qapi/util.h         | 2 +-
>  include/qapi/visitor-impl.h | 6 +++---
>  include/qapi/visitor.h      | 2 +-
>  include/qom/object.h        | 2 +-
>  qapi/qapi-dealloc-visitor.c | 3 ++-
>  qapi/qapi-util.c            | 2 +-
>  qapi/qapi-visit-core.c      | 6 +++---
>  qom/object.c                | 2 +-
>  scripts/qapi-types.py       | 4 ++--
>  10 files changed, 16 insertions(+), 15 deletions(-)
> 
> diff --git a/include/hw/qdev-core.h b/include/hw/qdev-core.h
> index 4e673f9..913963e 100644
> --- a/include/hw/qdev-core.h
> +++ b/include/hw/qdev-core.h
> @@ -236,7 +236,7 @@ struct Property {
>  struct PropertyInfo {
>      const char *name;
>      const char *description;
> -    const char **enum_table;
> +    const char * const *enum_table;
>      int (*print)(DeviceState *dev, Property *prop, char *dest, size_t len);
>      ObjectPropertyAccessor *get;
>      ObjectPropertyAccessor *set;
> diff --git a/include/qapi/util.h b/include/qapi/util.h
> index de9238b..7ad26c0 100644
> --- a/include/qapi/util.h
> +++ b/include/qapi/util.h
> @@ -11,7 +11,7 @@
>  #ifndef QAPI_UTIL_H
>  #define QAPI_UTIL_H
>  
> -int qapi_enum_parse(const char *lookup[], const char *buf,
> +int qapi_enum_parse(const char * const lookup[], const char *buf,
>                      int max, int def, Error **errp);
>  
>  #endif
> diff --git a/include/qapi/visitor-impl.h b/include/qapi/visitor-impl.h
> index 09bb0fd..f4a2f74 100644
> --- a/include/qapi/visitor-impl.h
> +++ b/include/qapi/visitor-impl.h
> @@ -30,7 +30,7 @@ struct Visitor
>      GenericList *(*next_list)(Visitor *v, GenericList **list, Error **errp);
>      void (*end_list)(Visitor *v, Error **errp);
>  
> -    void (*type_enum)(Visitor *v, int *obj, const char *strings[],
> +    void (*type_enum)(Visitor *v, int *obj, const char * const strings[],
>                        const char *kind, const char *name, Error **errp);
>      void (*get_next_type)(Visitor *v, int *kind, const int *qobjects,
>                            const char *name, Error **errp);
> @@ -59,9 +59,9 @@ struct Visitor
>      void (*end_union)(Visitor *v, bool data_present, Error **errp);
>  };
>  
> -void input_type_enum(Visitor *v, int *obj, const char *strings[],
> +void input_type_enum(Visitor *v, int *obj, const char * const strings[],
>                       const char *kind, const char *name, Error **errp);
> -void output_type_enum(Visitor *v, int *obj, const char *strings[],
> +void output_type_enum(Visitor *v, int *obj, const char * const strings[],
>                        const char *kind, const char *name, Error **errp);
>  
>  #endif
> diff --git a/include/qapi/visitor.h b/include/qapi/visitor.h
> index 5934f59..00ba104 100644
> --- a/include/qapi/visitor.h
> +++ b/include/qapi/visitor.h
> @@ -43,7 +43,7 @@ void visit_optional(Visitor *v, bool *present, const char *name,
>                      Error **errp);
>  void visit_get_next_type(Visitor *v, int *obj, const int *qtypes,
>                           const char *name, Error **errp);
> -void visit_type_enum(Visitor *v, int *obj, const char *strings[],
> +void visit_type_enum(Visitor *v, int *obj, const char * const strings[],
>                       const char *kind, const char *name, Error **errp);
>  void visit_type_int(Visitor *v, int64_t *obj, const char *name, Error **errp);
>  void visit_type_uint8(Visitor *v, uint8_t *obj, const char *name, Error **errp);
> diff --git a/include/qom/object.h b/include/qom/object.h
> index 223b577..dfdba2f 100644
> --- a/include/qom/object.h
> +++ b/include/qom/object.h
> @@ -1011,7 +1011,7 @@ int64_t object_property_get_int(Object *obj, const char *name,
>   * an enum).
>   */
>  int object_property_get_enum(Object *obj, const char *name,
> -                             const char *strings[], Error **errp);
> +                             const char * const strings[], Error **errp);
>  
>  /**
>   * object_property_get_uint16List:
> diff --git a/qapi/qapi-dealloc-visitor.c b/qapi/qapi-dealloc-visitor.c
> index a14a1c7..d7f92c5 100644
> --- a/qapi/qapi-dealloc-visitor.c
> +++ b/qapi/qapi-dealloc-visitor.c
> @@ -156,7 +156,8 @@ static void qapi_dealloc_type_size(Visitor *v, uint64_t *obj, const char *name,
>  {
>  }
>  
> -static void qapi_dealloc_type_enum(Visitor *v, int *obj, const char *strings[],
> +static void qapi_dealloc_type_enum(Visitor *v, int *obj,
> +                                   const char * const strings[],
>                                     const char *kind, const char *name,
>                                     Error **errp)
>  {
> diff --git a/qapi/qapi-util.c b/qapi/qapi-util.c
> index 1d8fb96..bcdc94d 100644
> --- a/qapi/qapi-util.c
> +++ b/qapi/qapi-util.c
> @@ -14,7 +14,7 @@
>  #include "qapi/error.h"
>  #include "qapi/util.h"
>  
> -int qapi_enum_parse(const char *lookup[], const char *buf,
> +int qapi_enum_parse(const char * const lookup[], const char *buf,
>                      int max, int def, Error **errp)
>  {
>      int i;
> diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
> index b66b93a..a18ba16 100644
> --- a/qapi/qapi-visit-core.c
> +++ b/qapi/qapi-visit-core.c
> @@ -89,7 +89,7 @@ void visit_get_next_type(Visitor *v, int *obj, const int *qtypes,
>      }
>  }
>  
> -void visit_type_enum(Visitor *v, int *obj, const char *strings[],
> +void visit_type_enum(Visitor *v, int *obj, const char * const strings[],
>                       const char *kind, const char *name, Error **errp)
>  {
>      v->type_enum(v, obj, strings, kind, name, errp);
> @@ -260,7 +260,7 @@ void visit_type_number(Visitor *v, double *obj, const char *name, Error **errp)
>      v->type_number(v, obj, name, errp);
>  }
>  
> -void output_type_enum(Visitor *v, int *obj, const char *strings[],
> +void output_type_enum(Visitor *v, int *obj, const char * const strings[],
>                        const char *kind, const char *name,
>                        Error **errp)
>  {
> @@ -279,7 +279,7 @@ void output_type_enum(Visitor *v, int *obj, const char *strings[],
>      visit_type_str(v, &enum_str, name, errp);
>  }
>  
> -void input_type_enum(Visitor *v, int *obj, const char *strings[],
> +void input_type_enum(Visitor *v, int *obj, const char * const strings[],
>                       const char *kind, const char *name,
>                       Error **errp)
>  {
> diff --git a/qom/object.c b/qom/object.c
> index 29c7aea..2534398 100644
> --- a/qom/object.c
> +++ b/qom/object.c
> @@ -1025,7 +1025,7 @@ int64_t object_property_get_int(Object *obj, const char *name,
>  }
>  
>  int object_property_get_enum(Object *obj, const char *name,
> -                             const char *strings[], Error **errp)
> +                             const char * const strings[], Error **errp)
>  {
>      StringOutputVisitor *sov;
>      StringInputVisitor *siv;
> diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
> index db87218..c8d6db6 100644
> --- a/scripts/qapi-types.py
> +++ b/scripts/qapi-types.py
> @@ -118,7 +118,7 @@ struct %(name)s
>  
>  def generate_enum_lookup(name, values):
>      ret = mcgen('''
> -const char *%(name)s_lookup[] = {
> +const char * const %(name)s_lookup[] = {
>  ''',
>                           name=name)
>      i = 0
> @@ -140,7 +140,7 @@ const char *%(name)s_lookup[] = {
>  
>  def generate_enum(name, values):
>      lookup_decl = mcgen('''
> -extern const char *%(name)s_lookup[];
> +extern const char * const %(name)s_lookup[];
>  ''',
>                  name=name)
>  
> 

Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 06/34] qom: add a object_property_add_enum helper method
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 06/34] qom: add a object_property_add_enum helper method Daniel P. Berrange
@ 2015-04-17 14:56   ` Paolo Bonzini
  2015-04-17 15:01     ` Paolo Bonzini
  0 siblings, 1 reply; 71+ messages in thread
From: Paolo Bonzini @ 2015-04-17 14:56 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Gerd Hoffmann, Stefan Hajnoczi



On 17/04/2015 16:22, Daniel P. Berrange wrote:
> A QOM property can be parsed as enum using the visit_type_enum()
> helper method, but this forces callers to use the more complex
> generic object_property_add() method when registering it. It
> also requires that users of that object have access to the
> string map when they want to read the property value.
> 
> This patch introduces a specialized object_property_add_enum()
> method which simplifies the use of enum properties, so the
> setters/getters directly get passed the int value.
> 
>   typedef enum {
>      MYDEV_TYPE_FROG,
>      MYDEV_TYPE_ALLIGATOR,
>      MYDEV_TYPE_PLATYPUS,
> 
>      MYDEV_TYPE_LAST
>   } MyDevType;
> 
> Then provide a table of enum <-> string mappings
> 
>   static const char *const mydevtypemap[MYDEV_TYPE_LAST + 1] = {
>      [MYDEV_TYPE_FROG] = "frog",
>      [MYDEV_TYPE_ALLIGATOR] = "alligator",
>      [MYDEV_TYPE_PLATYPUS] = "platypus",
>      [MYDEV_TYPE_LAST] = NULL,
>   };
> 
> Assuming an object struct of
> 
>    typedef struct {
>       Object parent;
>       MyDevType devtype;
>       ...other fields...
>    } MyDev;
> 
> The property can then be registered as follows:
> 
>    static int mydev_prop_get_devtype(Object *obj,
>                                      Error **errp G_GNUC_UNUSED)
>    {
>        MyDev *dev = MYDEV(obj);
> 
>        return dev->devtype;
>    }
> 
>    static void mydev_prop_set_devtype(Object *obj,
>                                       int value,
>                                       Error **errp G_GNUC_UNUSED)
>    {
>        MyDev *dev = MYDEV(obj);
> 
>        dev->endpoint = value;
>    }
> 
>    object_property_add_enum(obj, "devtype",
>                             mydevtypemap,
>                             mydev_prop_get_devtype,
>                             mydev_prop_set_devtype,
>                             NULL);
> 
> Note there is no need to check the range of 'value' in
> the setter, because the string->enum conversion code will
> have already done that and reported an error as required.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  include/qom/object.h | 17 ++++++++++++++++
>  qom/object.c         | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 74 insertions(+)
> 
> diff --git a/include/qom/object.h b/include/qom/object.h
> index dfdba2f..3462821 100644
> --- a/include/qom/object.h
> +++ b/include/qom/object.h
> @@ -1262,6 +1262,23 @@ void object_property_add_bool(Object *obj, const char *name,
>                                Error **errp);
>  
>  /**
> + * object_property_add_enum:
> + * @obj: the object to add a property to
> + * @name: the name of the property
> + * @get: the getter or NULL if the property is write-only.
> + * @set: the setter or NULL if the property is read-only
> + * @errp: if an error occurs, a pointer to an area to store the error
> + *
> + * Add a enum property using getters/setters.  This function will add a
> + * property of type 'enum'.
> + */
> +void object_property_add_enum(Object *obj, const char *name,
> +                              const char * const *strings,
> +                              int (*get)(Object *, Error **),
> +                              void (*set)(Object *, int, Error **),
> +                              Error **errp);
> +
> +/**
>   * object_property_add_tm:
>   * @obj: the object to add a property to
>   * @name: the name of the property
> diff --git a/qom/object.c b/qom/object.c
> index 2534398..543cc57 100644
> --- a/qom/object.c
> +++ b/qom/object.c
> @@ -1607,6 +1607,63 @@ void object_property_add_bool(Object *obj, const char *name,
>      }
>  }
>  
> +typedef struct EnumProperty {
> +    const char * const *strings;
> +    int (*get)(Object *, Error **);
> +    void (*set)(Object *, int, Error **);
> +} EnumProperty;
> +
> +static void property_get_enum(Object *obj, Visitor *v, void *opaque,
> +                              const char *name, Error **errp)
> +{
> +    EnumProperty *prop = opaque;
> +    int value;
> +
> +    value = prop->get(obj, errp);
> +    visit_type_enum(v, &value, prop->strings, NULL, name, errp);
> +}
> +
> +static void property_set_enum(Object *obj, Visitor *v, void *opaque,
> +                              const char *name, Error **errp)
> +{
> +    EnumProperty *prop = opaque;
> +    int value;
> +
> +    visit_type_enum(v, &value, prop->strings, NULL, name, errp);
> +    prop->set(obj, value, errp);
> +}
> +
> +static void property_release_enum(Object *obj, const char *name,
> +                                  void *opaque)
> +{
> +    EnumProperty *prop = opaque;
> +    g_free(prop);
> +}
> +
> +void object_property_add_enum(Object *obj, const char *name,
> +                              const char * const *strings,
> +                              int (*get)(Object *, Error **),
> +                              void (*set)(Object *, int, Error **),
> +                              Error **errp)
> +{
> +    Error *local_err = NULL;
> +    EnumProperty *prop = g_malloc0(sizeof(*prop));
> +
> +    prop->strings = strings;
> +    prop->get = get;
> +    prop->set = set;
> +
> +    object_property_add(obj, name, "enum",
> +                        get ? property_get_enum : NULL,
> +                        set ? property_set_enum : NULL,
> +                        property_release_enum,
> +                        prop, &local_err);
> +    if (local_err) {
> +        error_propagate(errp, local_err);
> +        g_free(prop);
> +    }
> +}
> +
>  typedef struct TMProperty {
>      void (*get)(Object *, struct tm *, Error **);
>  } TMProperty;
> 

Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 06/34] qom: add a object_property_add_enum helper method
  2015-04-17 14:56   ` Paolo Bonzini
@ 2015-04-17 15:01     ` Paolo Bonzini
  2015-04-17 15:11       ` Daniel P. Berrange
  0 siblings, 1 reply; 71+ messages in thread
From: Paolo Bonzini @ 2015-04-17 15:01 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Gerd Hoffmann, Stefan Hajnoczi



On 17/04/2015 16:56, Paolo Bonzini wrote:
> 
> 
> On 17/04/2015 16:22, Daniel P. Berrange wrote:
>> A QOM property can be parsed as enum using the visit_type_enum()
>> helper method, but this forces callers to use the more complex
>> generic object_property_add() method when registering it. It
>> also requires that users of that object have access to the
>> string map when they want to read the property value.
>>
>> This patch introduces a specialized object_property_add_enum()
>> method which simplifies the use of enum properties, so the
>> setters/getters directly get passed the int value.
>>
>>   typedef enum {
>>      MYDEV_TYPE_FROG,
>>      MYDEV_TYPE_ALLIGATOR,
>>      MYDEV_TYPE_PLATYPUS,
>>
>>      MYDEV_TYPE_LAST
>>   } MyDevType;
>>
>> Then provide a table of enum <-> string mappings
>>
>>   static const char *const mydevtypemap[MYDEV_TYPE_LAST + 1] = {
>>      [MYDEV_TYPE_FROG] = "frog",
>>      [MYDEV_TYPE_ALLIGATOR] = "alligator",
>>      [MYDEV_TYPE_PLATYPUS] = "platypus",
>>      [MYDEV_TYPE_LAST] = NULL,
>>   };
>>
>> Assuming an object struct of
>>
>>    typedef struct {
>>       Object parent;
>>       MyDevType devtype;
>>       ...other fields...
>>    } MyDev;
>>
>> The property can then be registered as follows:
>>
>>    static int mydev_prop_get_devtype(Object *obj,
>>                                      Error **errp G_GNUC_UNUSED)
>>    {
>>        MyDev *dev = MYDEV(obj);
>>
>>        return dev->devtype;
>>    }
>>
>>    static void mydev_prop_set_devtype(Object *obj,
>>                                       int value,
>>                                       Error **errp G_GNUC_UNUSED)
>>    {
>>        MyDev *dev = MYDEV(obj);
>>
>>        dev->endpoint = value;
>>    }
>>
>>    object_property_add_enum(obj, "devtype",
>>                             mydevtypemap,
>>                             mydev_prop_get_devtype,
>>                             mydev_prop_set_devtype,
>>                             NULL);

On second thought (after seeing patch 7), please add a property type
argument here.  We lose introspection of enum property types otherwise.

It's possible to use macros so that 'MyEnum' gets translated to two
arguments '"MyEnum", MyEnum_lookup', but I don't think that's worth the
obfuscation.

Paolo

>> Note there is no need to check the range of 'value' in
>> the setter, because the string->enum conversion code will
>> have already done that and reported an error as required.
>>
>> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
>> ---
>>  include/qom/object.h | 17 ++++++++++++++++
>>  qom/object.c         | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>>  2 files changed, 74 insertions(+)
>>
>> diff --git a/include/qom/object.h b/include/qom/object.h
>> index dfdba2f..3462821 100644
>> --- a/include/qom/object.h
>> +++ b/include/qom/object.h
>> @@ -1262,6 +1262,23 @@ void object_property_add_bool(Object *obj, const char *name,
>>                                Error **errp);
>>  
>>  /**
>> + * object_property_add_enum:
>> + * @obj: the object to add a property to
>> + * @name: the name of the property
>> + * @get: the getter or NULL if the property is write-only.
>> + * @set: the setter or NULL if the property is read-only
>> + * @errp: if an error occurs, a pointer to an area to store the error
>> + *
>> + * Add a enum property using getters/setters.  This function will add a
>> + * property of type 'enum'.
>> + */
>> +void object_property_add_enum(Object *obj, const char *name,
>> +                              const char * const *strings,
>> +                              int (*get)(Object *, Error **),
>> +                              void (*set)(Object *, int, Error **),
>> +                              Error **errp);
>> +
>> +/**
>>   * object_property_add_tm:
>>   * @obj: the object to add a property to
>>   * @name: the name of the property
>> diff --git a/qom/object.c b/qom/object.c
>> index 2534398..543cc57 100644
>> --- a/qom/object.c
>> +++ b/qom/object.c
>> @@ -1607,6 +1607,63 @@ void object_property_add_bool(Object *obj, const char *name,
>>      }
>>  }
>>  
>> +typedef struct EnumProperty {
>> +    const char * const *strings;
>> +    int (*get)(Object *, Error **);
>> +    void (*set)(Object *, int, Error **);
>> +} EnumProperty;
>> +
>> +static void property_get_enum(Object *obj, Visitor *v, void *opaque,
>> +                              const char *name, Error **errp)
>> +{
>> +    EnumProperty *prop = opaque;
>> +    int value;
>> +
>> +    value = prop->get(obj, errp);
>> +    visit_type_enum(v, &value, prop->strings, NULL, name, errp);
>> +}
>> +
>> +static void property_set_enum(Object *obj, Visitor *v, void *opaque,
>> +                              const char *name, Error **errp)
>> +{
>> +    EnumProperty *prop = opaque;
>> +    int value;
>> +
>> +    visit_type_enum(v, &value, prop->strings, NULL, name, errp);
>> +    prop->set(obj, value, errp);
>> +}
>> +
>> +static void property_release_enum(Object *obj, const char *name,
>> +                                  void *opaque)
>> +{
>> +    EnumProperty *prop = opaque;
>> +    g_free(prop);
>> +}
>> +
>> +void object_property_add_enum(Object *obj, const char *name,
>> +                              const char * const *strings,
>> +                              int (*get)(Object *, Error **),
>> +                              void (*set)(Object *, int, Error **),
>> +                              Error **errp)
>> +{
>> +    Error *local_err = NULL;
>> +    EnumProperty *prop = g_malloc0(sizeof(*prop));
>> +
>> +    prop->strings = strings;
>> +    prop->get = get;
>> +    prop->set = set;
>> +
>> +    object_property_add(obj, name, "enum",
>> +                        get ? property_get_enum : NULL,
>> +                        set ? property_set_enum : NULL,
>> +                        property_release_enum,
>> +                        prop, &local_err);
>> +    if (local_err) {
>> +        error_propagate(errp, local_err);
>> +        g_free(prop);
>> +    }
>> +}
>> +
>>  typedef struct TMProperty {
>>      void (*get)(Object *, struct tm *, Error **);
>>  } TMProperty;
>>
> 
> Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
> 
> 

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 07/34] qom: don't pass string table to object_get_enum method
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 07/34] qom: don't pass string table to object_get_enum method Daniel P. Berrange
@ 2015-04-17 15:05   ` Paolo Bonzini
  0 siblings, 0 replies; 71+ messages in thread
From: Paolo Bonzini @ 2015-04-17 15:05 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Gerd Hoffmann, Stefan Hajnoczi



On 17/04/2015 16:22, Daniel P. Berrange wrote:
> Now that properties can be explicitly registered as an enum
> type, there is no need to pass the string table to the
> object_get_enum method. The object property registration
> already has a pointer to the string table.
> 
> In changing this method signature, the hostmem backend object
> has to be converted to use the new enum property registration
> code, which simplifies it somewhat.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  backends/hostmem.c   | 22 ++++++++--------------
>  include/qom/object.h |  3 +--
>  numa.c               |  1 -
>  qom/object.c         | 32 ++++++++++++++++++++++++--------
>  4 files changed, 33 insertions(+), 25 deletions(-)
> 
> diff --git a/backends/hostmem.c b/backends/hostmem.c
> index b7b6cf8..c3b9df1 100644
> --- a/backends/hostmem.c
> +++ b/backends/hostmem.c
> @@ -113,24 +113,17 @@ host_memory_backend_set_host_nodes(Object *obj, Visitor *v, void *opaque,
>  #endif
>  }
>  
> -static void
> -host_memory_backend_get_policy(Object *obj, Visitor *v, void *opaque,
> -                               const char *name, Error **errp)
> +static int
> +host_memory_backend_get_policy(Object *obj, Error **errp G_GNUC_UNUSED)
>  {
>      HostMemoryBackend *backend = MEMORY_BACKEND(obj);
> -    int policy = backend->policy;
> -
> -    visit_type_enum(v, &policy, HostMemPolicy_lookup, NULL, name, errp);
> +    return backend->policy;
>  }
>  
>  static void
> -host_memory_backend_set_policy(Object *obj, Visitor *v, void *opaque,
> -                               const char *name, Error **errp)
> +host_memory_backend_set_policy(Object *obj, int policy, Error **errp)
>  {
>      HostMemoryBackend *backend = MEMORY_BACKEND(obj);
> -    int policy;
> -
> -    visit_type_enum(v, &policy, HostMemPolicy_lookup, NULL, name, errp);
>      backend->policy = policy;
>  
>  #ifndef CONFIG_NUMA
> @@ -252,9 +245,10 @@ static void host_memory_backend_init(Object *obj)
>      object_property_add(obj, "host-nodes", "int",
>                          host_memory_backend_get_host_nodes,
>                          host_memory_backend_set_host_nodes, NULL, NULL, NULL);
> -    object_property_add(obj, "policy", "str",
> -                        host_memory_backend_get_policy,
> -                        host_memory_backend_set_policy, NULL, NULL, NULL);
> +    object_property_add_enum(obj, "policy",
> +                             HostMemPolicy_lookup,
> +                             host_memory_backend_get_policy,
> +                             host_memory_backend_set_policy, NULL);
>  }
>  
>  MemoryRegion *
> diff --git a/include/qom/object.h b/include/qom/object.h
> index 3462821..bfad22f 100644
> --- a/include/qom/object.h
> +++ b/include/qom/object.h
> @@ -1003,7 +1003,6 @@ int64_t object_property_get_int(Object *obj, const char *name,
>   * object_property_get_enum:
>   * @obj: the object
>   * @name: the name of the property
> - * @strings: strings corresponding to enums
>   * @errp: returns an error if this function fails
>   *
>   * Returns: the value of the property, converted to an integer, or
> @@ -1011,7 +1010,7 @@ int64_t object_property_get_int(Object *obj, const char *name,
>   * an enum).
>   */
>  int object_property_get_enum(Object *obj, const char *name,
> -                             const char * const strings[], Error **errp);
> +                             Error **errp);
>  
>  /**
>   * object_property_get_uint16List:
> diff --git a/numa.c b/numa.c
> index c975fb2..881a123 100644
> --- a/numa.c
> +++ b/numa.c
> @@ -457,7 +457,6 @@ static int query_memdev(Object *obj, void *opaque)
>  
>          m->value->policy = object_property_get_enum(obj,
>                                                      "policy",
> -                                                    HostMemPolicy_lookup,
>                                                      &err);
>          if (err) {
>              goto error;
> diff --git a/qom/object.c b/qom/object.c
> index 543cc57..e99b3c9 100644
> --- a/qom/object.c
> +++ b/qom/object.c
> @@ -1024,13 +1024,35 @@ int64_t object_property_get_int(Object *obj, const char *name,
>      return retval;
>  }
>  
> +typedef struct EnumProperty {
> +    const char * const *strings;
> +    int (*get)(Object *, Error **);
> +    void (*set)(Object *, int, Error **);
> +} EnumProperty;
> +
> +
>  int object_property_get_enum(Object *obj, const char *name,
> -                             const char * const strings[], Error **errp)
> +                             Error **errp)
>  {
>      StringOutputVisitor *sov;
>      StringInputVisitor *siv;
>      char *str;
>      int ret;
> +    ObjectProperty *prop = object_property_find(obj, name, errp);
> +    EnumProperty *enumprop;
> +
> +    if (prop == NULL) {
> +        return 0;
> +    }
> +
> +    if (!g_str_equal(prop->type, "enum")) {

Inspecting the type is a bit gross, and in fact it breaks if you keep
the QAPI type name in patch 6.  The HostMemPolicy conversion is
certainly okay, but unfortunately this is not.

If you decide to go with macros in patch 6, perhaps you can do the same
here so that object_property_get_enum(obj, "policy", HostMemPolicy,
&err) can compare against the "HostMemPolicy" type.

The fact that HostMemPolicy_lookup exists is then a (mild, but
sufficient) guarantee that the opaque pointer of the property is really
an EnumProperty pointer.

Paolo

> +        error_setg(errp, "Property %s on %s is not an 'enum' type",
> +                   name, object_class_get_name(
> +                       object_get_class(obj)));
> +        return 0;
> +    }
> +
> +    enumprop = prop->opaque;
>  
>      sov = string_output_visitor_new(false);
>      object_property_get(obj, string_output_get_visitor(sov), name, errp);
> @@ -1038,7 +1060,7 @@ int object_property_get_enum(Object *obj, const char *name,
>      siv = string_input_visitor_new(str);
>      string_output_visitor_cleanup(sov);
>      visit_type_enum(string_input_get_visitor(siv),
> -                    &ret, strings, NULL, name, errp);
> +                    &ret, enumprop->strings, NULL, name, errp);
>  
>      g_free(str);
>      string_input_visitor_cleanup(siv);
> @@ -1607,12 +1629,6 @@ void object_property_add_bool(Object *obj, const char *name,
>      }
>  }
>  
> -typedef struct EnumProperty {
> -    const char * const *strings;
> -    int (*get)(Object *, Error **);
> -    void (*set)(Object *, int, Error **);
> -} EnumProperty;
> -
>  static void property_get_enum(Object *obj, Visitor *v, void *opaque,
>                                const char *name, Error **errp)
>  {
> 

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 06/34] qom: add a object_property_add_enum helper method
  2015-04-17 15:01     ` Paolo Bonzini
@ 2015-04-17 15:11       ` Daniel P. Berrange
  2015-04-17 15:19         ` Paolo Bonzini
  0 siblings, 1 reply; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 15:11 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: qemu-devel, Stefan Hajnoczi, Gerd Hoffmann

On Fri, Apr 17, 2015 at 05:01:24PM +0200, Paolo Bonzini wrote:
> 
> 
> On 17/04/2015 16:56, Paolo Bonzini wrote:
> > 
> > 
> > On 17/04/2015 16:22, Daniel P. Berrange wrote:
> >> A QOM property can be parsed as enum using the visit_type_enum()
> >> helper method, but this forces callers to use the more complex
> >> generic object_property_add() method when registering it. It
> >> also requires that users of that object have access to the
> >> string map when they want to read the property value.
> >>
> >> This patch introduces a specialized object_property_add_enum()
> >> method which simplifies the use of enum properties, so the
> >> setters/getters directly get passed the int value.
> >>
> >>   typedef enum {
> >>      MYDEV_TYPE_FROG,
> >>      MYDEV_TYPE_ALLIGATOR,
> >>      MYDEV_TYPE_PLATYPUS,
> >>
> >>      MYDEV_TYPE_LAST
> >>   } MyDevType;
> >>
> >> Then provide a table of enum <-> string mappings
> >>
> >>   static const char *const mydevtypemap[MYDEV_TYPE_LAST + 1] = {
> >>      [MYDEV_TYPE_FROG] = "frog",
> >>      [MYDEV_TYPE_ALLIGATOR] = "alligator",
> >>      [MYDEV_TYPE_PLATYPUS] = "platypus",
> >>      [MYDEV_TYPE_LAST] = NULL,
> >>   };
> >>
> >> Assuming an object struct of
> >>
> >>    typedef struct {
> >>       Object parent;
> >>       MyDevType devtype;
> >>       ...other fields...
> >>    } MyDev;
> >>
> >> The property can then be registered as follows:
> >>
> >>    static int mydev_prop_get_devtype(Object *obj,
> >>                                      Error **errp G_GNUC_UNUSED)
> >>    {
> >>        MyDev *dev = MYDEV(obj);
> >>
> >>        return dev->devtype;
> >>    }
> >>
> >>    static void mydev_prop_set_devtype(Object *obj,
> >>                                       int value,
> >>                                       Error **errp G_GNUC_UNUSED)
> >>    {
> >>        MyDev *dev = MYDEV(obj);
> >>
> >>        dev->endpoint = value;
> >>    }
> >>
> >>    object_property_add_enum(obj, "devtype",
> >>                             mydevtypemap,
> >>                             mydev_prop_get_devtype,
> >>                             mydev_prop_set_devtype,
> >>                             NULL);
> 
> On second thought (after seeing patch 7), please add a property type
> argument here.  We lose introspection of enum property types otherwise.

Can you give me a pointer to where we lose introspection ? The hostmem
code just registers its 'policy' property with a type name of 'str',
which I've just changed to 'enum' in patch 7, so I didn't think I was
regressing anything. Is there some QMP command which you think looses
the info that I can verify.

> It's possible to use macros so that 'MyEnum' gets translated to two
> arguments '"MyEnum", MyEnum_lookup', but I don't think that's worth the
> obfuscation.


Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 04/34] qom: add object_new_propv / object_new_proplist constructors
  2015-04-17 14:55   ` Paolo Bonzini
@ 2015-04-17 15:16     ` Daniel P. Berrange
  0 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 15:16 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: qemu-devel, Stefan Hajnoczi, Gerd Hoffmann

On Fri, Apr 17, 2015 at 04:55:26PM +0200, Paolo Bonzini wrote:
> 
> 
> On 17/04/2015 16:22, Daniel P. Berrange wrote:
> > +
> > +Object *object_new_proplist(const char *typename,
> > +                            const char *id,
> > +                            Error **errp,
> > +                            va_list vargs)
> > +{
> > +    Object *obj;
> > +    const char *propname;
> > +
> > +    obj = object_new(typename);
> > +
> > +    if (object_class_is_abstract(object_get_class(obj))) {
> > +        error_setg(errp, "object type '%s' is abstract", typename);
> > +        goto error;
> > +    }
> > +
> > +    propname = va_arg(vargs, char *);
> > +    while (propname != NULL) {
> > +        const char *value = va_arg(vargs, char *);
> > +
> > +        object_property_parse(obj, value, propname, errp);
> > +        if (*errp) {
> > +            goto error;
> > +        }
> > +        propname = va_arg(vargs, char *);
> > +    }
> > +
> > +    object_property_add_child(container_get(object_get_root(), "/objects"),
> > +                              id, obj, errp);
> 
> I would pass the parent as an additional argument to
> object_new_proplist().  Otherwise looks good.

Yep, that does rather make sense for flexibility.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 25/34] io: add QIOTask class for async operations
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 25/34] io: add QIOTask class for async operations Daniel P. Berrange
@ 2015-04-17 15:16   ` Paolo Bonzini
  2015-04-17 15:49     ` Daniel P. Berrange
  0 siblings, 1 reply; 71+ messages in thread
From: Paolo Bonzini @ 2015-04-17 15:16 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Gerd Hoffmann, Stefan Hajnoczi



On 17/04/2015 16:22, Daniel P. Berrange wrote:
> A number of I/O operations need to be performed asynchronously
> to avoid blocking the main loop. The caller of such APIs need
> to provide a callback to be invoked on completion/error and
> need access to the error, if any. The small QIOTask provides
> a simple framework for dealing with such probes. The API
> docs inline provide an outline of how this is to be used.
> 
> In this series, the QIOTask class will be used for things like
> the TLS handshake, the websockets handshake and TCP connect()
> progress.

Is this actually worth making it a heavyweight QOM object?  In general
if you don't do object_property_add_child (and I don't think here you
do), it's simpler to just make it a C struct.

In this case I even think you're leaking the task.  You do object_ref
twice (once at creation time, once before qio_channel_add_watch_full)
and only have one object_unref.  I would just do without reference
counting, and add a qio_task_free() function that calls
qio_task_finalize() and frees the QIOTask.

BTW, do you have plans to use the GDestroyNotify argument to
qio_task_new, or is it just for consistency?

Paolo

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 06/34] qom: add a object_property_add_enum helper method
  2015-04-17 15:11       ` Daniel P. Berrange
@ 2015-04-17 15:19         ` Paolo Bonzini
  2015-04-17 15:22           ` Daniel P. Berrange
  0 siblings, 1 reply; 71+ messages in thread
From: Paolo Bonzini @ 2015-04-17 15:19 UTC (permalink / raw)
  To: Daniel P. Berrange; +Cc: qemu-devel, Stefan Hajnoczi, Gerd Hoffmann



On 17/04/2015 17:11, Daniel P. Berrange wrote:
> > On second thought (after seeing patch 7), please add a property type
> > argument here.  We lose introspection of enum property types otherwise.
> 
> Can you give me a pointer to where we lose introspection ? The hostmem
> code just registers its 'policy' property with a type name of 'str',

That was already a bug. :(  It should have been registered as 
"HostMemPolicy".  See for example:

$ qemu-system-x86_64 -device ide-hd,help
ide-hd.bootindex=int32
ide-hd.unit=uint32
ide-hd.bios-chs-trans=BiosAtaTranslation (Logical CHS translation algorithm, auto/none/lba/large/rechs)
...

> which I've just changed to 'enum' in patch 7, so I didn't think I was
> regressing anything. Is there some QMP command which you think looses
> the info that I can verify.

The qom-list command returns a list of property names and types.

Paolo

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 06/34] qom: add a object_property_add_enum helper method
  2015-04-17 15:19         ` Paolo Bonzini
@ 2015-04-17 15:22           ` Daniel P. Berrange
  0 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 15:22 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: qemu-devel, Stefan Hajnoczi, Gerd Hoffmann

On Fri, Apr 17, 2015 at 05:19:31PM +0200, Paolo Bonzini wrote:
> 
> 
> On 17/04/2015 17:11, Daniel P. Berrange wrote:
> > > On second thought (after seeing patch 7), please add a property type
> > > argument here.  We lose introspection of enum property types otherwise.
> > 
> > Can you give me a pointer to where we lose introspection ? The hostmem
> > code just registers its 'policy' property with a type name of 'str',
> 
> That was already a bug. :(  It should have been registered as 
> "HostMemPolicy".  See for example:

Ok, next time I'll probably send a patch which fixes that bug independantly
and then does the conversion, in case we want the fix on stable too.

> $ qemu-system-x86_64 -device ide-hd,help
> ide-hd.bootindex=int32
> ide-hd.unit=uint32
> ide-hd.bios-chs-trans=BiosAtaTranslation (Logical CHS translation algorithm, auto/none/lba/large/rechs)
> ...
> 
> > which I've just changed to 'enum' in patch 7, so I didn't think I was
> > regressing anything. Is there some QMP command which you think looses
> > the info that I can verify.
> 
> The qom-list command returns a list of property names and types.

Thanks, will look at that.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 23/34] io: add QIOChannelSocket class
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 23/34] io: add QIOChannelSocket class Daniel P. Berrange
@ 2015-04-17 15:28   ` Paolo Bonzini
  2015-04-17 15:52     ` Daniel P. Berrange
  2015-04-20  7:18   ` Gerd Hoffmann
  1 sibling, 1 reply; 71+ messages in thread
From: Paolo Bonzini @ 2015-04-17 15:28 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Gerd Hoffmann, Stefan Hajnoczi



On 17/04/2015 16:22, Daniel P. Berrange wrote:
> Implement a QIOChannel subclass that supports sockets I/O
> 
> TBD check errno handling of windows port & fix watch impl
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  include/io/channel-socket.h | 168 +++++++++++++
>  io/Makefile.objs            |   1 +
>  io/channel-socket.c         | 572 ++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 741 insertions(+)
>  create mode 100644 include/io/channel-socket.h
>  create mode 100644 io/channel-socket.c
> 
> diff --git a/include/io/channel-socket.h b/include/io/channel-socket.h
> new file mode 100644
> index 0000000..b95349b
> --- /dev/null
> +++ b/include/io/channel-socket.h
> @@ -0,0 +1,168 @@
> +/*
> + * QEMU I/O channels sockets driver
> + *
> + * Copyright (c) 2015 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#ifndef QIO_CHANNEL_SOCKET_H__
> +#define QIO_CHANNEL_SOCKET_H__
> +
> +#include "io/channel.h"
> +#include "qemu/sockets.h"
> +
> +#define TYPE_QIO_CHANNEL_SOCKET "qemu:io-channel-socket"
> +#define QIO_CHANNEL_SOCKET(obj)                                     \
> +    OBJECT_CHECK(QIOChannelSocket, (obj), TYPE_QIO_CHANNEL_SOCKET)
> +
> +typedef struct QIOChannelSocket QIOChannelSocket;
> +
> +/**
> + * QIOChannelSocket:
> + *
> + * The QIOChannelSocket class provides a channel implementation
> + * that can transport data over a UNIX socket or TCP socket.
> + * Beyond the core channel API, it also provides functionality
> + * for accepting client connections, tuning some socket
> + * parameters and getting socket address strings.
> + */
> +
> +struct QIOChannelSocket {
> +    QIOChannel parent;
> +    int fd;
> +    struct sockaddr_storage localAddr;
> +    socklen_t localAddrLen;
> +    struct sockaddr_storage remoteAddr;
> +    socklen_t remoteAddrLen;
> +};
> +
> +
> +/**
> + * qio_channel_socket_new_fd:
> + * @fd: the socket file descriptor
> + * @errp: pointer to an uninitialized error object
> + *
> + * Create a channel for performing I/O on the socket
> + * connection represented by the file descriptor @fd.
> + *
> + * Returns: the socket channel object, or NULL on error
> + */
> +QIOChannelSocket *
> +qio_channel_socket_new_fd(int fd,
> +                          Error **errp);
> +
> +/**
> + * qio_channel_socket_get_local_addr_string:
> + * @ioc: the socket channel object
> + * @hostname: pointer to be filled with hostname string
> + * @servicename: pointer to be filled with servicename string
> + * @family: pointer to be filled with network address family
> + * @errp: pointer to an uninitialized error object
> + *
> + * Get the string representation of the local socket
> + * address. The address information will be stored in
> + * the @hostname, @servicename and @family parameters.
> + * The @hostname and @servicename strings will be
> + * allocated to the size required and should be free
> + * with g_free() when no longer required
> + *
> + * Returns: 0 on success, -1 on error
> + */
> +int
> +qio_channel_socket_get_local_addr_string(QIOChannelSocket *ioc,
> +                                         char **hostname,
> +                                         char **servicename,
> +                                         NetworkAddressFamily *family,
> +                                         Error **errp);
> +
> +/**
> + * qio_channel_socket_get_remote_addr_string:
> + * @ioc: the socket channel object
> + * @hostname: pointer to be filled with hostname string
> + * @servicename: pointer to be filled with servicename string
> + * @family: pointer to be filled with network address family
> + * @errp: pointer to an uninitialized error object
> + *
> + * Get the string representation of the remote socket
> + * address. The address information will be stored in
> + * the @hostname, @servicename and @family parameters.
> + * The @hostname and @servicename strings will be
> + * allocated to the size required and should be free
> + * with g_free() when no longer required
> + *
> + * Returns: 0 on success, -1 on error
> + */
> +int
> +qio_channel_socket_get_remote_addr_string(QIOChannelSocket *ioc,
> +                                          char **hostname,
> +                                          char **servicename,
> +                                          NetworkAddressFamily *family,
> +                                          Error **errp);

Would it be possible to change these to use a SocketAddress* type?

> +
> +/**
> + * qio_channel_socket_set_nodelay:
> + * @ioc: the socket channel object
> + * @enabled: the new flag state
> + *
> + * Set the state of the NODELAY socket flag. If the

The function name is okay, but please write TCP_NODELAY in the
documentation, or talk about Nagle's algorithm instead of mentioning the
flag.

> + * @enabled parameter is true, then NODELAY will be
> + * set and data will be transmitted immediately. If
> + * @enabled is false, then data may be temporarily
> + * held for transmission to enable writes to be
> + * coallesced.
> + */
> +void
> +qio_channel_socket_set_nodelay(QIOChannelSocket *ioc,
> +                               bool enabled);
> +
> +/**
> + * qio_channel_socket_accept:
> + * @ioc: the socket channel object
> + * @errp: pointer to an uninitialized error object
> + *
> + * If the socket represents a server, then this accepts
> + * a new client connection. The returned channel will
> + * represent the connected client socket.
> + *
> + * Returns: the new client channel, or NULL on error
> + */
> +QIOChannelSocket *
> +qio_channel_socket_accept(QIOChannelSocket *ioc,
> +                          Error **errp);

Does it make sense for a passive socket to be a QIOChannelSocket?  We
have already a pretty decent API in util/qemu-sockets.c, and
QIOChannelSocket will become more similar to qemu-sockets if you switch
to SocketAddress.  Perhaps this function can just take a file descriptor?

Paolo

> +
> +typedef enum {
> +    QIO_CHANNEL_SOCKET_SHUTDOWN_BOTH,
> +    QIO_CHANNEL_SOCKET_SHUTDOWN_READ,
> +    QIO_CHANNEL_SOCKET_SHUTDOWN_WRITE,
> +} QIOChannelSocketShutdown;
> +
> +/**
> + * qio_channel_socket_shutdown:
> + * @ioc: the socket channel object
> + * @how: the direction to shutdown
> + * @errp: pointer to an uninitialized error object
> + *
> + * Shutdowns transmission or receiving on a socket
> + * without closing the socket file descriptor.
> + *
> + * Returns: 0 on success, -1 on error
> + */
> +int
> +qio_channel_socket_shutdown(QIOChannelSocket *ioc,
> +                            QIOChannelSocketShutdown how,
> +                            Error **errp);
> +
> +#endif /* QIO_CHANNEL_SOCKET_H__ */
> diff --git a/io/Makefile.objs b/io/Makefile.objs
> index a776676..4f5e276 100644
> --- a/io/Makefile.objs
> +++ b/io/Makefile.objs
> @@ -1,2 +1,3 @@
>  util-obj-y += channel.o
>  util-obj-y += channel-unix.o
> +util-obj-y += channel-socket.o
> diff --git a/io/channel-socket.c b/io/channel-socket.c
> new file mode 100644
> index 0000000..5eacf38
> --- /dev/null
> +++ b/io/channel-socket.c
> @@ -0,0 +1,572 @@
> +/*
> + * QEMU I/O channels sockets driver
> + *
> + * Copyright (c) 2015 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include <glib/gi18n.h>
> +
> +#include "io/channel-socket.h"
> +#include "io/channel-unix.h"
> +
> +#define SOCKET_MAX_FDS 16
> +
> +static int
> +qio_channel_socket_get_addr_string(struct sockaddr_storage *sa,
> +                                   socklen_t salen,
> +                                   char **hostname,
> +                                   char **servicename,
> +                                   NetworkAddressFamily *family,
> +                                   Error **errp)
> +{
> +    char host[NI_MAXHOST];
> +    char serv[NI_MAXSERV];
> +    int ret;
> +
> +    ret = getnameinfo((struct sockaddr *)sa, salen,
> +                      host, sizeof(host),
> +                      serv, sizeof(serv),
> +                      NI_NUMERICHOST | NI_NUMERICSERV);
> +    if (ret != 0) {
> +        error_setg(errp, "Cannot format numeric socket address: %s\n",
> +                   gai_strerror(ret));
> +        return -1;
> +    }
> +
> +    *hostname = g_strdup(host);
> +    *servicename = g_strdup(serv);
> +    *family = inet_netfamily(sa->ss_family);
> +    return 0;
> +}
> +
> +int
> +qio_channel_socket_get_local_addr_string(QIOChannelSocket *ioc,
> +                                         char **hostname,
> +                                         char **servicename,
> +                                         NetworkAddressFamily *family,
> +                                         Error **errp)
> +{
> +    return qio_channel_socket_get_addr_string(&ioc->localAddr,
> +                                              ioc->localAddrLen,
> +                                              hostname,
> +                                              servicename,
> +                                              family,
> +                                              errp);
> +}
> +
> +int
> +qio_channel_socket_get_remote_addr_string(QIOChannelSocket *ioc,
> +                                          char **hostname,
> +                                          char **servicename,
> +                                          NetworkAddressFamily *family,
> +                                          Error **errp)
> +{
> +    return qio_channel_socket_get_addr_string(&ioc->remoteAddr,
> +                                              ioc->remoteAddrLen,
> +                                              hostname,
> +                                              servicename,
> +                                              family,
> +                                              errp);
> +}
> +
> +QIOChannelSocket *
> +qio_channel_socket_new_fd(int fd,
> +                          Error **errp)
> +{
> +    QIOChannelSocket *ioc;
> +
> +    ioc = QIO_CHANNEL_SOCKET(object_new(TYPE_QIO_CHANNEL_SOCKET));
> +
> +    ioc->fd = fd;
> +    ioc->remoteAddrLen = sizeof(ioc->remoteAddr);
> +    ioc->localAddrLen = sizeof(ioc->localAddr);
> +
> +    if (getpeername(fd, (struct sockaddr *)&ioc->remoteAddr,
> +                    &ioc->remoteAddrLen) < 0) {
> +        if (socket_error() == ENOTCONN) {
> +            memset(&ioc->remoteAddr, 0, sizeof(ioc->remoteAddr));
> +            ioc->remoteAddrLen = sizeof(ioc->remoteAddr);
> +        } else {
> +            error_setg_errno(errp, socket_error(), "%s",
> +                             _("Unable to query remote socket address"));
> +            goto error;
> +        }
> +    }
> +
> +    if (getsockname(fd, (struct sockaddr *)&ioc->localAddr,
> +                    &ioc->localAddrLen) < 0) {
> +        error_setg_errno(errp, socket_error(), "%s",
> +                         _("Unable to query local socket address"));
> +        goto error;
> +    }
> +
> +    return ioc;
> +
> + error:
> +    ioc->fd = -1; /* Let the caller close FD on failure */
> +    object_unref(OBJECT(ioc));
> +    return NULL;
> +}
> +
> +QIOChannelSocket *
> +qio_channel_socket_accept(QIOChannelSocket *ioc,
> +                          Error **errp)
> +{
> +    QIOChannelSocket *cioc;
> +
> +    cioc = QIO_CHANNEL_SOCKET(object_new(TYPE_QIO_CHANNEL_SOCKET));
> +    cioc->fd = -1;
> +    cioc->remoteAddrLen = sizeof(ioc->remoteAddr);
> +    cioc->localAddrLen = sizeof(ioc->localAddr);
> +
> + retry:
> +    cioc->fd = accept(ioc->fd, (struct sockaddr *)&cioc->remoteAddr,
> +                      &cioc->remoteAddrLen);
> +    if (cioc->fd < 0) {
> +        if (socket_error() == EINTR) {
> +            goto retry;
> +        }
> +        goto error;
> +    }
> +
> +    if (getsockname(cioc->fd, (struct sockaddr *)&ioc->localAddr,
> +                    &ioc->localAddrLen) < 0) {
> +        error_setg_errno(errp, socket_error(), "%s",
> +                         _("Unable to query local socket address"));
> +        goto error;
> +    }
> +
> +    return cioc;
> +
> + error:
> +    object_unref(OBJECT(cioc));
> +    return NULL;
> +}
> +
> +static void qio_channel_socket_init(Object *obj)
> +{
> +    QIOChannelSocket *ioc = QIO_CHANNEL_SOCKET(obj);
> +    ioc->fd = -1;
> +}
> +
> +static void qio_channel_socket_finalize(Object *obj)
> +{
> +    QIOChannelSocket *ioc = QIO_CHANNEL_SOCKET(obj);
> +    if (ioc->fd != -1) {
> +        close(ioc->fd);
> +        ioc->fd = -1;
> +    }
> +}
> +
> +static bool qio_channel_socket_has_feature(QIOChannel *ioc,
> +                                           QIOChannelFeature feature)
> +{
> +    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
> +
> +    switch (feature) {
> +    case QIO_CHANNEL_FEATURE_FD_PASS:
> +#ifndef WIN32
> +        if (sioc->localAddr.ss_family == AF_UNIX) {
> +            return true;
> +        } else {
> +#endif /* WIN32 */
> +            return false;
> +#ifndef WIN32
> +        }
> +#endif /* WIN32 */
> +    default:
> +        return false;
> +    }
> +}
> +
> +
> +#ifndef WIN32
> +static void qio_channel_socket_copy_fds(struct msghdr *msg,
> +                                        int **fds, size_t *nfds)
> +{
> +    struct cmsghdr *cmsg;
> +
> +    *nfds = 0;
> +    *fds = NULL;
> +
> +    for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
> +        int fd_size, i;
> +        int gotfds;
> +
> +        if (cmsg->cmsg_len < CMSG_LEN(sizeof(int)) ||
> +            cmsg->cmsg_level != SOL_SOCKET ||
> +            cmsg->cmsg_type != SCM_RIGHTS) {
> +            continue;
> +        }
> +
> +        fd_size = cmsg->cmsg_len - CMSG_LEN(0);
> +
> +        if (!fd_size) {
> +            continue;
> +        }
> +
> +        gotfds = fd_size / sizeof(int);
> +        *fds = g_renew(int, *fds, *nfds + gotfds);
> +        memcpy(*fds + *nfds, CMSG_DATA(cmsg), fd_size);
> +
> +        for (i = 0; i < gotfds; i++) {
> +            int fd = (*fds)[*nfds + i];
> +            if (fd < 0) {
> +                continue;
> +            }
> +
> +            /* O_NONBLOCK is preserved across SCM_RIGHTS so reset it */
> +            qemu_set_block(fd);
> +
> +#ifndef MSG_CMSG_CLOEXEC
> +            qemu_set_cloexec(fd);
> +#endif
> +        }
> +        *nfds += gotfds;
> +    }
> +}
> +
> +
> +static ssize_t qio_channel_socket_readv(QIOChannel *ioc,
> +                                        const struct iovec *iov,
> +                                        size_t niov,
> +                                        int **fds,
> +                                        size_t *nfds,
> +                                        int flags,
> +                                        Error **errp)
> +{
> +    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
> +    ssize_t ret;
> +    struct msghdr msg = { NULL, };
> +    char control[CMSG_SPACE(sizeof(int) * SOCKET_MAX_FDS)];
> +    int sflags = 0;
> +
> +    if (flags & ~QIO_CHANNEL_READ_PEEK) {
> +        error_setg_errno(errp, EINVAL,
> +                         _("Flags %x are not supported"),
> +                         flags & ~QIO_CHANNEL_READ_PEEK);
> +        return -1;
> +    }
> +
> +#ifdef MSG_CMSG_CLOEXEC
> +    sflags |= MSG_CMSG_CLOEXEC;
> +#endif
> +    if (flags & QIO_CHANNEL_READ_PEEK) {
> +        sflags |= MSG_PEEK;
> +    }
> +
> +    msg.msg_iov = (struct iovec *)iov;
> +    msg.msg_iovlen = niov;
> +    if (fds && nfds) {
> +        msg.msg_control = control;
> +        msg.msg_controllen = sizeof(control);
> +    }
> +
> + retry:
> +    ret = recvmsg(sioc->fd, &msg, sflags);
> +    if (ret < 0) {
> +        if (socket_error() == EAGAIN) {
> +            return -2;
> +        }
> +        if (socket_error() == EINTR) {
> +            goto retry;
> +        }
> +
> +        error_setg_errno(errp, socket_error(), "%s",
> +                         _("Unable to read from socket"));
> +        return -1;
> +    }
> +
> +    if (fds && nfds) {
> +        qio_channel_socket_copy_fds(&msg, fds, nfds);
> +    }
> +
> +    return ret;
> +}
> +
> +static ssize_t qio_channel_socket_writev(QIOChannel *ioc,
> +                                         const struct iovec *iov,
> +                                         size_t niov,
> +                                         int *fds,
> +                                         size_t nfds,
> +                                         int flags G_GNUC_UNUSED,
> +                                         Error **errp)
> +{
> +    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
> +    ssize_t ret;
> +    struct msghdr msg = { NULL, };
> +
> +    if (flags) {
> +        error_setg_errno(errp, EINVAL,
> +                         _("Flags %x are not supported"),
> +                         flags);
> +        return -1;
> +    }
> +
> +    msg.msg_iov = (struct iovec *)iov;
> +    msg.msg_iovlen = niov;
> +
> +    if (nfds) {
> +        char control[CMSG_SPACE(sizeof(int) * SOCKET_MAX_FDS)];
> +        size_t fdsize = sizeof(int) * nfds;
> +        struct cmsghdr *cmsg;
> +
> +        if (nfds > SOCKET_MAX_FDS) {
> +            error_setg_errno(errp, -EINVAL,
> +                             _("Only %d FDs can be sent, got %zu"),
> +                             SOCKET_MAX_FDS, nfds);
> +            return -1;
> +        }
> +
> +        msg.msg_control = control;
> +        msg.msg_controllen = CMSG_SPACE(sizeof(int) * nfds);
> +
> +        cmsg = CMSG_FIRSTHDR(&msg);
> +        cmsg->cmsg_len = CMSG_LEN(fdsize);
> +        cmsg->cmsg_level = SOL_SOCKET;
> +        cmsg->cmsg_type = SCM_RIGHTS;
> +        memcpy(CMSG_DATA(cmsg), fds, fdsize);
> +    }
> +
> + retry:
> +    ret = sendmsg(sioc->fd, &msg, 0);
> +    if (ret <= 0) {
> +        if (socket_error() == EAGAIN) {
> +            return -2;
> +        }
> +        if (socket_error() == EINTR) {
> +            goto retry;
> +        }
> +        error_setg_errno(errp, socket_error(), "%s",
> +                         _("Unable to write to socket"));
> +        return -1;
> +    }
> +    return ret;
> +}
> +#else /* WIN32 */
> +static ssize_t qio_channel_socket_readv(QIOChannel *ioc,
> +                                        const struct iovec *iov,
> +                                        size_t niov,
> +                                        int **fds,
> +                                        size_t *nfds,
> +                                        int flags,
> +                                        Error **errp)
> +{
> +    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
> +    ssize_t done = 0;
> +    ssize_t i;
> +
> +    if (flags) {
> +        error_setg_errno(errp, EINVAL,
> +                         _("Flags %x are not supported"),
> +                         flags);
> +        return -1;
> +    }
> +    if (fds || nfds) {
> +        error_setg_errno(errp, EINVAL, "%s",
> +                         _("Channel does not support file descriptor passing"));
> +        return -1;
> +    }
> +
> +    for (i = 0; i < niov; i++) {
> +        ssize_t ret;
> +    retry:
> +        ret = recv(sioc->fd,
> +                   iov[i].iov_base,
> +                   iov[i].iov_len,
> +                   0);
> +        if (ret < 0) {
> +            if (socket_error() == EAGAIN) {
> +                if (done) {
> +                    return done;
> +                } else {
> +                    return QIO_CHANNEL_ERR_BLOCK;
> +                }
> +            } else if (socket_error() == EINTR) {
> +                goto retry;
> +            } else {
> +                error_setg_errno(errp, socket_error(), "%s",
> +                                 _("Unable to write to socket"));
> +                return -1;
> +            }
> +        }
> +        done += ret;
> +        if (ret < iov[i].iov_len) {
> +            return done;
> +        }
> +    }
> +
> +    return done;
> +}
> +
> +static ssize_t qio_channel_socket_writev(QIOChannel *ioc,
> +                                         const struct iovec *iov,
> +                                         size_t niov,
> +                                         int *fds,
> +                                         size_t nfds,
> +                                         int flags G_GNUC_UNUSED,
> +                                         Error **errp)
> +{
> +    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
> +    ssize_t done = 0;
> +    ssize_t i;
> +
> +    if (flags) {
> +        error_setg_errno(errp, EINVAL,
> +                         _("Flags %x are not supported"),
> +                         flags);
> +        return -1;
> +    }
> +    if (fds || nfds) {
> +        error_setg_errno(errp, EINVAL, "%s",
> +                         _("Channel does not support file descriptor passing"));
> +        return -1;
> +    }
> +
> +    for (i = 0; i < niov; i++) {
> +        ssize_t ret;
> +    retry:
> +        ret = send(sioc->fd,
> +                   iov[i].iov_base,
> +                   iov[i].iov_len,
> +                   0);
> +        if (ret < 0) {
> +            if (socket_error() == EAGAIN) {
> +                if (done) {
> +                    return done;
> +                } else {
> +                    return QIO_CHANNEL_ERR_BLOCK;
> +                }
> +            } else if (socket_error() == EINTR) {
> +                goto retry;
> +            } else {
> +                error_setg_errno(errp, socket_error(), "%s",
> +                                 _("Unable to write to socket"));
> +                return -1;
> +            }
> +        }
> +        done += ret;
> +        if (ret < iov[i].iov_len) {
> +            return done;
> +        }
> +    }
> +
> +    return done;
> +}
> +#endif /* WIN32 */
> +
> +static void qio_channel_socket_set_blocking(QIOChannel *ioc,
> +                                            bool enabled)
> +{
> +    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
> +
> +    if (enabled) {
> +        qemu_set_block(sioc->fd);
> +    } else {
> +        qemu_set_nonblock(sioc->fd);
> +    }
> +}
> +
> +void
> +qio_channel_socket_set_nodelay(QIOChannelSocket *ioc,
> +                               bool enabled)
> +{
> +    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
> +    int v = enabled ? 1 : 0;
> +
> +    qemu_setsockopt(sioc->fd, IPPROTO_TCP, TCP_NODELAY, &v, sizeof(v));
> +}
> +
> +static int qio_channel_socket_close(QIOChannel *ioc,
> +                                    Error **errp)
> +{
> +    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
> +
> +    if (closesocket(sioc->fd) < 0) {
> +        sioc->fd = -1;
> +        error_setg_errno(errp, socket_error(), "%s",
> +                         _("Unable to close socket"));
> +        return -1;
> +    }
> +    sioc->fd = -1;
> +    return 0;
> +}
> +
> +int
> +qio_channel_socket_shutdown(QIOChannelSocket *ioc,
> +                            QIOChannelSocketShutdown how,
> +                            Error **errp)
> +{
> +    int sockhow;
> +    switch (how) {
> +    case QIO_CHANNEL_SOCKET_SHUTDOWN_READ:
> +        sockhow = SHUT_RD;
> +        break;
> +    case QIO_CHANNEL_SOCKET_SHUTDOWN_WRITE:
> +        sockhow = SHUT_WR;
> +        break;
> +    case QIO_CHANNEL_SOCKET_SHUTDOWN_BOTH:
> +    default:
> +        sockhow = SHUT_RDWR;
> +        break;
> +    }
> +
> +    if (shutdown(ioc->fd, sockhow) < 0) {
> +        error_setg_errno(errp, socket_error(), "%s",
> +                         _("Unable to shutdown socket"));
> +        return -1;
> +    }
> +    return 0;
> +}
> +
> +static GSource *qio_channel_socket_create_watch(QIOChannel *ioc,
> +                                                GIOCondition condition)
> +{
> +    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
> +    return qio_channel_unix_create_fd_watch(ioc,
> +                                            sioc->fd,
> +                                            condition);
> +}
> +
> +static void qio_channel_socket_class_init(ObjectClass *klass,
> +                                          void *class_data G_GNUC_UNUSED)
> +{
> +    QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass);
> +
> +    ioc_klass->io_has_feature = qio_channel_socket_has_feature;
> +    ioc_klass->io_writev = qio_channel_socket_writev;
> +    ioc_klass->io_readv = qio_channel_socket_readv;
> +    ioc_klass->io_set_blocking = qio_channel_socket_set_blocking;
> +    ioc_klass->io_close = qio_channel_socket_close;
> +    ioc_klass->io_create_watch = qio_channel_socket_create_watch;
> +}
> +
> +static const TypeInfo qio_channel_socket_info = {
> +    .parent = TYPE_QIO_CHANNEL,
> +    .name = TYPE_QIO_CHANNEL_SOCKET,
> +    .instance_size = sizeof(QIOChannelSocket),
> +    .instance_init = qio_channel_socket_init,
> +    .instance_finalize = qio_channel_socket_finalize,
> +    .class_init = qio_channel_socket_class_init,
> +};
> +
> +static void qio_channel_socket_register_types(void)
> +{
> +    type_register_static(&qio_channel_socket_info);
> +}
> +
> +type_init(qio_channel_socket_register_types);
> 

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 25/34] io: add QIOTask class for async operations
  2015-04-17 15:16   ` Paolo Bonzini
@ 2015-04-17 15:49     ` Daniel P. Berrange
  2015-04-17 15:57       ` Paolo Bonzini
  0 siblings, 1 reply; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 15:49 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: qemu-devel, Stefan Hajnoczi, Gerd Hoffmann

On Fri, Apr 17, 2015 at 05:16:26PM +0200, Paolo Bonzini wrote:
> 
> 
> On 17/04/2015 16:22, Daniel P. Berrange wrote:
> > A number of I/O operations need to be performed asynchronously
> > to avoid blocking the main loop. The caller of such APIs need
> > to provide a callback to be invoked on completion/error and
> > need access to the error, if any. The small QIOTask provides
> > a simple framework for dealing with such probes. The API
> > docs inline provide an outline of how this is to be used.
> > 
> > In this series, the QIOTask class will be used for things like
> > the TLS handshake, the websockets handshake and TCP connect()
> > progress.
> 
> Is this actually worth making it a heavyweight QOM object?  In general
> if you don't do object_property_add_child (and I don't think here you
> do), it's simpler to just make it a C struct.

Essentially I just used QOM anywhere that I wanted to have ref
counting, even if I didn't need the property feature. I figure
this particular usage isn't performance critical so using the
more heavyweight QOM framework wasn't the end of the world.

> In this case I even think you're leaking the task.  You do object_ref
> twice (once at creation time, once before qio_channel_add_watch_full)
> and only have one object_unref.  I would just do without reference
> counting, and add a qio_task_free() function that calls
> qio_task_finalize() and frees the QIOTask.

Are you referring to the qio_channel_tls_handshake() method in the
next patch ? If so it does actually have two object_unref calls
so shouldn't be leaking. In more complex scenarios I thnk the
ref counting ability will come in useful. Of course I could add
ref counting to a plain struct without using QOM, but it felt
better to just use QOM and be done with it, so people don't have
to remember which particular unref method they must use.

> BTW, do you have plans to use the GDestroyNotify argument to
> qio_task_new, or is it just for consistency?

I'm not 100% sure if I'll need it or not yet. One of my todo items
is to double check the sequence of operations when a VNC/chardev
client disconnects while a background task is in progress. It is
possible I might need to hold a reference on the VNC/chardev state
in which case the GDestroyNotify arg will come in useful. So I just
added it for consistency initially.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 23/34] io: add QIOChannelSocket class
  2015-04-17 15:28   ` Paolo Bonzini
@ 2015-04-17 15:52     ` Daniel P. Berrange
  2015-04-17 16:00       ` Paolo Bonzini
  0 siblings, 1 reply; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 15:52 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: qemu-devel, Stefan Hajnoczi, Gerd Hoffmann

On Fri, Apr 17, 2015 at 05:28:09PM +0200, Paolo Bonzini wrote:
> 
> 
> On 17/04/2015 16:22, Daniel P. Berrange wrote:
> > Implement a QIOChannel subclass that supports sockets I/O
> > 
> > TBD check errno handling of windows port & fix watch impl
> > 
> > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> > ---
> >  include/io/channel-socket.h | 168 +++++++++++++
> >  io/Makefile.objs            |   1 +
> >  io/channel-socket.c         | 572 ++++++++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 741 insertions(+)
> >  create mode 100644 include/io/channel-socket.h
> >  create mode 100644 io/channel-socket.c
> > 
> > diff --git a/include/io/channel-socket.h b/include/io/channel-socket.h
> > new file mode 100644
> > index 0000000..b95349b
> > --- /dev/null
> > +++ b/include/io/channel-socket.h


> > +/**
> > + * qio_channel_socket_get_local_addr_string:
> > + * @ioc: the socket channel object
> > + * @hostname: pointer to be filled with hostname string
> > + * @servicename: pointer to be filled with servicename string
> > + * @family: pointer to be filled with network address family
> > + * @errp: pointer to an uninitialized error object
> > + *
> > + * Get the string representation of the local socket
> > + * address. The address information will be stored in
> > + * the @hostname, @servicename and @family parameters.
> > + * The @hostname and @servicename strings will be
> > + * allocated to the size required and should be free
> > + * with g_free() when no longer required
> > + *
> > + * Returns: 0 on success, -1 on error
> > + */
> > +int
> > +qio_channel_socket_get_local_addr_string(QIOChannelSocket *ioc,
> > +                                         char **hostname,
> > +                                         char **servicename,
> > +                                         NetworkAddressFamily *family,
> > +                                         Error **errp);
> > +
> > +/**
> > + * qio_channel_socket_get_remote_addr_string:
> > + * @ioc: the socket channel object
> > + * @hostname: pointer to be filled with hostname string
> > + * @servicename: pointer to be filled with servicename string
> > + * @family: pointer to be filled with network address family
> > + * @errp: pointer to an uninitialized error object
> > + *
> > + * Get the string representation of the remote socket
> > + * address. The address information will be stored in
> > + * the @hostname, @servicename and @family parameters.
> > + * The @hostname and @servicename strings will be
> > + * allocated to the size required and should be free
> > + * with g_free() when no longer required
> > + *
> > + * Returns: 0 on success, -1 on error
> > + */
> > +int
> > +qio_channel_socket_get_remote_addr_string(QIOChannelSocket *ioc,
> > +                                          char **hostname,
> > +                                          char **servicename,
> > +                                          NetworkAddressFamily *family,
> > +                                          Error **errp);
> 
> Would it be possible to change these to use a SocketAddress* type?

Yeah, that looks like it is a viable option.

> > +
> > +/**
> > + * qio_channel_socket_set_nodelay:
> > + * @ioc: the socket channel object
> > + * @enabled: the new flag state
> > + *
> > + * Set the state of the NODELAY socket flag. If the
> 
> The function name is okay, but please write TCP_NODELAY in the
> documentation, or talk about Nagle's algorithm instead of mentioning the
> flag.

Sure will do.

> > + * @enabled parameter is true, then NODELAY will be
> > + * set and data will be transmitted immediately. If
> > + * @enabled is false, then data may be temporarily
> > + * held for transmission to enable writes to be
> > + * coallesced.
> > + */
> > +void
> > +qio_channel_socket_set_nodelay(QIOChannelSocket *ioc,
> > +                               bool enabled);
> > +
> > +/**
> > + * qio_channel_socket_accept:
> > + * @ioc: the socket channel object
> > + * @errp: pointer to an uninitialized error object
> > + *
> > + * If the socket represents a server, then this accepts
> > + * a new client connection. The returned channel will
> > + * represent the connected client socket.
> > + *
> > + * Returns: the new client channel, or NULL on error
> > + */
> > +QIOChannelSocket *
> > +qio_channel_socket_accept(QIOChannelSocket *ioc,
> > +                          Error **errp);
> 
> Does it make sense for a passive socket to be a QIOChannelSocket?  We
> have already a pretty decent API in util/qemu-sockets.c, and
> QIOChannelSocket will become more similar to qemu-sockets if you switch
> to SocketAddress.  Perhaps this function can just take a file descriptor?

I was somewhat undecided about that really - One of my todos is to see
about better integrating with qemu-sockets for the connection facilities,
so will consider this problem too.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 01/34] ui: remove check for failure of qemu_acl_init()
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 01/34] ui: remove check for failure of qemu_acl_init() Daniel P. Berrange
@ 2015-04-17 15:56   ` Eric Blake
  0 siblings, 0 replies; 71+ messages in thread
From: Eric Blake @ 2015-04-17 15:56 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel
  Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

[-- Attachment #1: Type: text/plain, Size: 513 bytes --]

On 04/17/2015 08:22 AM, Daniel P. Berrange wrote:
> The qemu_acl_init() function has long since stopped being able
> to return NULL, since g_malloc will abort on OOM. As such the
> checks for NULL were unreachable code.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  ui/vnc.c | 8 --------
>  1 file changed, 8 deletions(-)

Reviewed-by: Eric Blake <eblake@redhat.com>

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 25/34] io: add QIOTask class for async operations
  2015-04-17 15:49     ` Daniel P. Berrange
@ 2015-04-17 15:57       ` Paolo Bonzini
  2015-04-17 16:11         ` Daniel P. Berrange
  0 siblings, 1 reply; 71+ messages in thread
From: Paolo Bonzini @ 2015-04-17 15:57 UTC (permalink / raw)
  To: Daniel P. Berrange; +Cc: qemu-devel, Stefan Hajnoczi, Gerd Hoffmann



On 17/04/2015 17:49, Daniel P. Berrange wrote:
> > In this case I even think you're leaking the task.  You do object_ref
> > twice (once at creation time, once before qio_channel_add_watch_full)
> > and only have one object_unref.  I would just do without reference
> > counting, and add a qio_task_free() function that calls
> > qio_task_finalize() and frees the QIOTask.
> 
> Are you referring to the qio_channel_tls_handshake() method in the
> next patch ? If so it does actually have two object_unref calls
> so shouldn't be leaking. In more complex scenarios I thnk the
> ref counting ability will come in useful. Of course I could add
> ref counting to a plain struct without using QOM, but it felt
> better to just use QOM and be done with it, so people don't have
> to remember which particular unref method they must use.

I cannot find the second... O:-)

> > BTW, do you have plans to use the GDestroyNotify argument to
> > qio_task_new, or is it just for consistency?
> 
> I'm not 100% sure if I'll need it or not yet. One of my todo items
> is to double check the sequence of operations when a VNC/chardev
> client disconnects while a background task is in progress. It is
> possible I might need to hold a reference on the VNC/chardev state
> in which case the GDestroyNotify arg will come in useful. So I just
> added it for consistency initially.

Yup, no problem there.

Paolo

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 23/34] io: add QIOChannelSocket class
  2015-04-17 15:52     ` Daniel P. Berrange
@ 2015-04-17 16:00       ` Paolo Bonzini
  0 siblings, 0 replies; 71+ messages in thread
From: Paolo Bonzini @ 2015-04-17 16:00 UTC (permalink / raw)
  To: Daniel P. Berrange; +Cc: qemu-devel, Stefan Hajnoczi, Gerd Hoffmann



On 17/04/2015 17:52, Daniel P. Berrange wrote:
>>> > > +QIOChannelSocket *
>>> > > +qio_channel_socket_accept(QIOChannelSocket *ioc,
>>> > > +                          Error **errp);
>> > 
>> > Does it make sense for a passive socket to be a QIOChannelSocket?  We
>> > have already a pretty decent API in util/qemu-sockets.c, and
>> > QIOChannelSocket will become more similar to qemu-sockets if you switch
>> > to SocketAddress.  Perhaps this function can just take a file descriptor?
> I was somewhat undecided about that really - One of my todos is to see
> about better integrating with qemu-sockets for the connection facilities,
> so will consider this problem too.

Hmm, I guess it makes sense to have the passive socket as a QOM object,
so it is okay.

Paolo

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 04/34] qom: add object_new_propv / object_new_proplist constructors
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 04/34] qom: add object_new_propv / object_new_proplist constructors Daniel P. Berrange
  2015-04-17 14:55   ` Paolo Bonzini
@ 2015-04-17 16:11   ` Eric Blake
  1 sibling, 0 replies; 71+ messages in thread
From: Eric Blake @ 2015-04-17 16:11 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel
  Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

[-- Attachment #1: Type: text/plain, Size: 3438 bytes --]

On 04/17/2015 08:22 AM, Daniel P. Berrange wrote:
> It is reasonably common to want to create an object, set a
> number of properties, register it in the hierarchy and then
> mark it as complete (if a user creatable type). This requires
> quite alot of error prone, verbose, boilerplate code to achieve.

s/alot/a lot/

> 
> The object_new_propv / object_new_proplist constructors will
> simplify this task by performing all required steps in one go,
> accepting the property names/values as variadic args.
> 
> Usage would be:
> 
>    Error *err = NULL;
>    Object *obj;
>    obj = object_new_propv(TYPE_MEMORY_BACKEND_FILE,
>                           "hostmem0",
>                           &err,
>                           "share", "yes",
>                           "mem-path", "/dev/shm/somefile",
>                           "prealloc", "yes",
>                           "size": "1048576",

s/:/,/

>                           NULL);
> 
> Note all property values are passed in string form and will
> be parsed into their required data types.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  include/qom/object.h | 58 +++++++++++++++++++++++++++++++++++++++++++++++
>  qom/object.c         | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  tests/Makefile       |  2 +-
>  3 files changed, 123 insertions(+), 1 deletion(-)
> 
> diff --git a/include/qom/object.h b/include/qom/object.h
> index d2d7748..223b577 100644
> --- a/include/qom/object.h

> + *
> + *   obj = object_new_propv(TYPE_MEMORY_BACKEND_FILE,
> + *                          "hostmem0",
> + *                          &err,
> + *                          "share", "yes",
> + *                          "mem-path", "/dev/shm/somefile",
> + *                          "prealloc", "yes",
> + *                          "size": "1048576",

and again

> + *                          NULL);
> + *
> + *   if (!obj) {
> + *     g_printerr("Cannot create memory backend: %s\n",
> + *                error_get_pretty(err));
> + *   }
> + *
> + * Returns: The newly allocated, instantiated & initialized object.
> + */
> +Object *object_new_propv(const char *typename,
> +                         const char *id,
> +                         Error **errp,
> +                         ...);

You probably want to use the gcc __attribute__((__sentinel__)), so that
the compiler can ensure that the caller NULL-terminates their list.


> +Object *object_new_proplist(const char *typename,
> +                            const char *id,
> +                            Error **errp,
> +                            va_list vargs)
> +{
> +    Object *obj;
> +    const char *propname;
> +
> +    obj = object_new(typename);
> +
> +    if (object_class_is_abstract(object_get_class(obj))) {
> +        error_setg(errp, "object type '%s' is abstract", typename);
> +        goto error;
> +    }
> +
> +    propname = va_arg(vargs, char *);
> +    while (propname != NULL) {
> +        const char *value = va_arg(vargs, char *);
> +
> +        object_property_parse(obj, value, propname, errp);

Is it worth a sanity check of assert(value) prior to calling
object_property_parse(), as I have the suspicion that it doesn't handle
NULL very well?

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 25/34] io: add QIOTask class for async operations
  2015-04-17 15:57       ` Paolo Bonzini
@ 2015-04-17 16:11         ` Daniel P. Berrange
  2015-04-17 17:06           ` Paolo Bonzini
  0 siblings, 1 reply; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 16:11 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: qemu-devel, Stefan Hajnoczi, Gerd Hoffmann

On Fri, Apr 17, 2015 at 05:57:24PM +0200, Paolo Bonzini wrote:
> 
> 
> On 17/04/2015 17:49, Daniel P. Berrange wrote:
> > > In this case I even think you're leaking the task.  You do object_ref
> > > twice (once at creation time, once before qio_channel_add_watch_full)
> > > and only have one object_unref.  I would just do without reference
> > > counting, and add a qio_task_free() function that calls
> > > qio_task_finalize() and frees the QIOTask.
> > 
> > Are you referring to the qio_channel_tls_handshake() method in the
> > next patch ? If so it does actually have two object_unref calls
> > so shouldn't be leaking. In more complex scenarios I thnk the
> > ref counting ability will come in useful. Of course I could add
> > ref counting to a plain struct without using QOM, but it felt
> > better to just use QOM and be done with it, so people don't have
> > to remember which particular unref method they must use.
> 
> I cannot find the second... O:-)

I create & unref it in the same place:

+    task = qio_task_new(OBJECT(ioc),
+                        func, opaque, destroy);
+
+    qio_channel_tls_handshake_task(ioc, task);
+
+    object_unref(OBJECT(task));



The second ref taken at time of qio_channel_add_watch_full() gets released
by the GDestroyNotify that is passed to that method.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 25/34] io: add QIOTask class for async operations
  2015-04-17 16:11         ` Daniel P. Berrange
@ 2015-04-17 17:06           ` Paolo Bonzini
  2015-04-17 17:38             ` Daniel P. Berrange
  0 siblings, 1 reply; 71+ messages in thread
From: Paolo Bonzini @ 2015-04-17 17:06 UTC (permalink / raw)
  To: Daniel P. Berrange; +Cc: qemu-devel, Stefan Hajnoczi, Gerd Hoffmann



On 17/04/2015 18:11, Daniel P. Berrange wrote:
> 
> +    task = qio_task_new(OBJECT(ioc),
> +                        func, opaque, destroy);
> +
> +    qio_channel_tls_handshake_task(ioc, task);
> +
> +    object_unref(OBJECT(task));
> 
> The second ref taken at time of qio_channel_add_watch_full() gets released
> by the GDestroyNotify that is passed to that method.

/me is blind.

So this means you really do not need the reference counting;
qio_channel_tls_handshake_task (which is static anyway) is what manages
the lifetime of the QIOTask, it can take ownership of the task right
after it is created.

In effect QIOTask is just wrapping the (func, opaque, destroy)
continuation.  It makes sense that it doesn't need reference counting,
because it has a well-defined point of death (just before the
continuation is called, or just before you find out it will never be
called).  So I think it's okay to remove the reference counting and make
it a simple heap-allocated struct.  gio_channel_tls_handshake_task can
free it after calling gio_task_{abort,complete}.

Without the reference counting the GDestroyNotify also becomes
unnecessary (and the fact that you're using QIOTask as a simple
continuation explains why you didn't need GDestroyNotify), but it's okay
if you want to leave it in.

I would also have QIOTaskFunc take the "exploded" struct, i.e. typedef
void QIOTaskFunc(QMyObject *, gpointer, Error *), and hide QIOTask
entirely from the user.

Paolo

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 25/34] io: add QIOTask class for async operations
  2015-04-17 17:06           ` Paolo Bonzini
@ 2015-04-17 17:38             ` Daniel P. Berrange
  0 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-17 17:38 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: qemu-devel, Stefan Hajnoczi, Gerd Hoffmann

On Fri, Apr 17, 2015 at 07:06:04PM +0200, Paolo Bonzini wrote:
> 
> 
> On 17/04/2015 18:11, Daniel P. Berrange wrote:
> > 
> > +    task = qio_task_new(OBJECT(ioc),
> > +                        func, opaque, destroy);
> > +
> > +    qio_channel_tls_handshake_task(ioc, task);
> > +
> > +    object_unref(OBJECT(task));
> > 
> > The second ref taken at time of qio_channel_add_watch_full() gets released
> > by the GDestroyNotify that is passed to that method.
> 
> /me is blind.
> 
> So this means you really do not need the reference counting;
> qio_channel_tls_handshake_task (which is static anyway) is what manages
> the lifetime of the QIOTask, it can take ownership of the task right
> after it is created.
> 
> In effect QIOTask is just wrapping the (func, opaque, destroy)
> continuation.  It makes sense that it doesn't need reference counting,
> because it has a well-defined point of death (just before the
> continuation is called, or just before you find out it will never be
> called).  So I think it's okay to remove the reference counting and make
> it a simple heap-allocated struct.  gio_channel_tls_handshake_task can
> free it after calling gio_task_{abort,complete}.
> 
> Without the reference counting the GDestroyNotify also becomes
> unnecessary (and the fact that you're using QIOTask as a simple
> continuation explains why you didn't need GDestroyNotify), but it's okay
> if you want to leave it in.
> 
> I would also have QIOTaskFunc take the "exploded" struct, i.e. typedef
> void QIOTaskFunc(QMyObject *, gpointer, Error *), and hide QIOTask
> entirely from the user.

Yeah, I can try that approach and see how it works out.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 34/34] char: introduce support for TLS encrypted TCP chardev backend
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 34/34] char: introduce support for TLS encrypted TCP chardev backend Daniel P. Berrange
@ 2015-04-17 18:27   ` Eric Blake
  2015-04-23 12:32     ` Daniel P. Berrange
  2015-05-04 20:07   ` Kashyap Chamarthy
  1 sibling, 1 reply; 71+ messages in thread
From: Eric Blake @ 2015-04-17 18:27 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel
  Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

[-- Attachment #1: Type: text/plain, Size: 4273 bytes --]

On 04/17/2015 08:22 AM, Daniel P. Berrange wrote:
> This integrates support for QIOChannelTLS object in the TCP
> chardev backend. If the 'tls-cred=NAME' option is passed with
> the '-chardev tcp' argument, then it will setup the chardev
> such that the client is required to establish a TLS handshake
> when connecting. The 'acl' option will further enable the
> creation of a 'char.$ID.tlspeername' ACL which will be used
> to validate the client x509 certificate, if provided.
> 
> A complete invokation to run QEMU as the server for a TLS

s/invokation/invocation/

> encrypted serial dev might be
> 
>   $ qemu-system-x86_64 \
>       -nodefconfig -nodefaults -device sga -display none \
>       -chardev socket,id=s0,host=127.0.0.1,port=9000,tls-cred=tls0,server \
>       -device isa-serial,chardev=s0 \
>       -object qcrypto-tls-cred,id=tls0,credtype=x509,\
>         endpoint=server,dir=/home/berrange/security/qemutls,verify-peer=off
> 
> To test with the gnutls-cli tool as the client:
> 
>   $ gnutls-cli --priority=NORMAL -p 9000 \
>        --x509cafile=/home/berrange/security/qemutls/ca-cert.pem \
>        127.0.0.1
> 
> If QEMU was told to use 'anon' credential type, then use the
> priority string 'NOMAL:+ANON-DH' with gnutls-cli

s/NOMAL/NORMAL/

> 
> Alternatively, if setting up a chardev to operate as a client,
> then the TLS credentials registered must be for the client
> endpoint. First a TLS server must be setup, which can be done
> with the gnutls-serv tool
> 
>   $ gnutls-serv --priority=NORMAL -p 9000 \
>        --x509cafile=/home/berrange/security/qemutls/ca-cert.pem \
>        --x509certfile=/home/berrange/security/qemutls/server-cert.pem \
>        --x509keyfile=/home/berrange/security/qemutls/server-key.pem
> 
> Then QEMU can connect with
> 
>   $ qemu-system-x86_64 \
>       -nodefconfig -nodefaults -device sga -display none \
>       -chardev socket,id=s0,host=127.0.0.1,port=9000,tls-cred=tls0 \
>       -device isa-serial,chardev=s0 \
>       -object qcrypto-tls-cred,id=tls0,credtype=x509,\
>         endpoint=client,dir=/home/berrange/security/qemutls
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  qapi-schema.json |   2 +
>  qemu-char.c      | 182 ++++++++++++++++++++++++++++++++++++++++++++++---------
>  qemu-options.hx  |   9 ++-
>  3 files changed, 161 insertions(+), 32 deletions(-)
> 
> diff --git a/qapi-schema.json b/qapi-schema.json
> index ac9594d..062a455 100644
> --- a/qapi-schema.json
> +++ b/qapi-schema.json
> @@ -2782,6 +2782,8 @@
>  # Since: 1.4
>  ##
>  { 'type': 'ChardevSocket', 'data': { 'addr'       : 'SocketAddress',
> +                                     '*tls-cred'  : 'str',
> +                                     '*acl'       : 'str',

Need to document these two fields, along with '(since 2.4)' designators.

> +++ b/qemu-options.hx
> @@ -2009,7 +2009,7 @@ ETEXI
>  DEF("chardev", HAS_ARG, QEMU_OPTION_chardev,
>      "-chardev null,id=id[,mux=on|off]\n"
>      "-chardev socket,id=id[,host=host],port=port[,to=to][,ipv4][,ipv6][,nodelay][,reconnect=seconds]\n"
> -    "         [,server][,nowait][,telnet][,reconnect=seconds][,mux=on|off] (tcp)\n"
> +    "         [,server][,nowait][,telnet][,reconnect=seconds][,mux=on|off][,tls-cred=ID][,acl] (tcp)\n"
>      "-chardev socket,id=id,path=path[,server][,nowait][,telnet][,reconnect=seconds][,mux=on|off] (unix)\n"
>      "-chardev udp,id=id[,host=host],port=port[,localaddr=localaddr]\n"
>      "         [,localport=localport][,ipv4][,ipv6][,mux=on|off]\n"
> @@ -2082,7 +2082,7 @@ Options to each backend are described below.
>  A void device. This device will not emit any data, and will drop any data it
>  receives. The null backend does not take any options.
>  
> -@item -chardev socket ,id=@var{id} [@var{TCP options} or @var{unix options}] [,server] [,nowait] [,telnet] [,reconnect=@var{seconds}]
> +@item -chardev socket ,id=@var{id} [@var{TCP options} or @var{unix options}] [,server] [,nowait] [,telnet] [,reconnect=@var{seconds}][,tls-cred=@var{id}]

Everyone else in this line had space before [

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 23/34] io: add QIOChannelSocket class
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 23/34] io: add QIOChannelSocket class Daniel P. Berrange
  2015-04-17 15:28   ` Paolo Bonzini
@ 2015-04-20  7:18   ` Gerd Hoffmann
  2015-04-23 12:31     ` Daniel P. Berrange
  1 sibling, 1 reply; 71+ messages in thread
From: Gerd Hoffmann @ 2015-04-20  7:18 UTC (permalink / raw)
  To: Daniel P. Berrange; +Cc: Paolo Bonzini, qemu-devel, Stefan Hajnoczi

On Fr, 2015-04-17 at 15:22 +0100, Daniel P. Berrange wrote:
> Implement a QIOChannel subclass that supports sockets I/O
> 
> TBD check errno handling of windows port & fix watch impl

> +struct QIOChannelSocket {
> +    QIOChannel parent;
> +    int fd;
> +    struct sockaddr_storage localAddr;
> +    socklen_t localAddrLen;
> +    struct sockaddr_storage remoteAddr;
> +    socklen_t remoteAddrLen;
> +};

Looks like this supports a single listening socket only, correct?  That
is a long-standing issue we have in qemu.  Listening on both ipv4 and
ipv6 doesn't work, except when binding the socket to the ipv6 wildcard
address which can accept ipv4 connects too on most systems.

Would be nice to tackle that while putting the socket code upside down
anyway.

cheers,
  Gerd

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels
  2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
                   ` (33 preceding siblings ...)
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 34/34] char: introduce support for TLS encrypted TCP chardev backend Daniel P. Berrange
@ 2015-04-23 12:28 ` Stefan Hajnoczi
  34 siblings, 0 replies; 71+ messages in thread
From: Stefan Hajnoczi @ 2015-04-23 12:28 UTC (permalink / raw)
  To: Daniel P. Berrange
  Cc: Paolo Bonzini, qemu-devel, Stefan Hajnoczi, Gerd Hoffmann

[-- Attachment #1: Type: text/plain, Size: 980 bytes --]

On Fri, Apr 17, 2015 at 03:22:03PM +0100, Daniel P. Berrange wrote:
> While I will probably start work on it, I'm not intending to submit
> the update of the NBD/migration code, until this series has been
> positively reviewed and looks like it is close to accepted for merge,
> as there are already enough patches in this series as it is :-) The
> aim though is to convert the NBD code to use QIOChannel instead of
> direct sockets usage & add the TLS protocol extension previously
> discussed with the NBD spec maintainer. The migration code will
> either be adapted to use QIOChannel, or the QEMUFile code will be
> adapted to use QIOChannel. Undecided which is the best approach there
> at this time. Probably depends whether we can do a QIOChannelRDMA
> impl that has performance on parity with what exists today.

Thanks for working on this.  I have only skimmed the patches but I think
your approach makes sense.

Looking forward to digging into NBD patches later.

Stefan

[-- Attachment #2: Type: application/pgp-signature, Size: 473 bytes --]

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 23/34] io: add QIOChannelSocket class
  2015-04-20  7:18   ` Gerd Hoffmann
@ 2015-04-23 12:31     ` Daniel P. Berrange
  0 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-23 12:31 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: Paolo Bonzini, qemu-devel, Stefan Hajnoczi

On Mon, Apr 20, 2015 at 09:18:49AM +0200, Gerd Hoffmann wrote:
> On Fr, 2015-04-17 at 15:22 +0100, Daniel P. Berrange wrote:
> > Implement a QIOChannel subclass that supports sockets I/O
> > 
> > TBD check errno handling of windows port & fix watch impl
> 
> > +struct QIOChannelSocket {
> > +    QIOChannel parent;
> > +    int fd;
> > +    struct sockaddr_storage localAddr;
> > +    socklen_t localAddrLen;
> > +    struct sockaddr_storage remoteAddr;
> > +    socklen_t remoteAddrLen;
> > +};
> 
> Looks like this supports a single listening socket only, correct?  That
> is a long-standing issue we have in qemu.  Listening on both ipv4 and
> ipv6 doesn't work, except when binding the socket to the ipv6 wildcard
> address which can accept ipv4 connects too on most systems.
> 
> Would be nice to tackle that while putting the socket code upside down
> anyway.

Thanks for reminding me about this limitation - I'll have a think about
how we can solve this problem at the same time too.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 34/34] char: introduce support for TLS encrypted TCP chardev backend
  2015-04-17 18:27   ` Eric Blake
@ 2015-04-23 12:32     ` Daniel P. Berrange
  0 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-04-23 12:32 UTC (permalink / raw)
  To: Eric Blake; +Cc: Paolo Bonzini, qemu-devel, Stefan Hajnoczi, Gerd Hoffmann

On Fri, Apr 17, 2015 at 12:27:06PM -0600, Eric Blake wrote:
> On 04/17/2015 08:22 AM, Daniel P. Berrange wrote:
> > This integrates support for QIOChannelTLS object in the TCP
> > chardev backend. If the 'tls-cred=NAME' option is passed with
> > the '-chardev tcp' argument, then it will setup the chardev
> > such that the client is required to establish a TLS handshake
> > when connecting. The 'acl' option will further enable the
> > creation of a 'char.$ID.tlspeername' ACL which will be used
> > to validate the client x509 certificate, if provided.
> > 
> > A complete invokation to run QEMU as the server for a TLS
> 
> s/invokation/invocation/
> 
> > encrypted serial dev might be
> > 
> >   $ qemu-system-x86_64 \
> >       -nodefconfig -nodefaults -device sga -display none \
> >       -chardev socket,id=s0,host=127.0.0.1,port=9000,tls-cred=tls0,server \
> >       -device isa-serial,chardev=s0 \
> >       -object qcrypto-tls-cred,id=tls0,credtype=x509,\
> >         endpoint=server,dir=/home/berrange/security/qemutls,verify-peer=off
> > 
> > To test with the gnutls-cli tool as the client:
> > 
> >   $ gnutls-cli --priority=NORMAL -p 9000 \
> >        --x509cafile=/home/berrange/security/qemutls/ca-cert.pem \
> >        127.0.0.1
> > 
> > If QEMU was told to use 'anon' credential type, then use the
> > priority string 'NOMAL:+ANON-DH' with gnutls-cli
> 
> s/NOMAL/NORMAL/
> 
> > 
> > Alternatively, if setting up a chardev to operate as a client,
> > then the TLS credentials registered must be for the client
> > endpoint. First a TLS server must be setup, which can be done
> > with the gnutls-serv tool
> > 
> >   $ gnutls-serv --priority=NORMAL -p 9000 \
> >        --x509cafile=/home/berrange/security/qemutls/ca-cert.pem \
> >        --x509certfile=/home/berrange/security/qemutls/server-cert.pem \
> >        --x509keyfile=/home/berrange/security/qemutls/server-key.pem
> > 
> > Then QEMU can connect with
> > 
> >   $ qemu-system-x86_64 \
> >       -nodefconfig -nodefaults -device sga -display none \
> >       -chardev socket,id=s0,host=127.0.0.1,port=9000,tls-cred=tls0 \
> >       -device isa-serial,chardev=s0 \
> >       -object qcrypto-tls-cred,id=tls0,credtype=x509,\
> >         endpoint=client,dir=/home/berrange/security/qemutls
> > 
> > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> > ---
> >  qapi-schema.json |   2 +
> >  qemu-char.c      | 182 ++++++++++++++++++++++++++++++++++++++++++++++---------
> >  qemu-options.hx  |   9 ++-
> >  3 files changed, 161 insertions(+), 32 deletions(-)
> > 
> > diff --git a/qapi-schema.json b/qapi-schema.json
> > index ac9594d..062a455 100644
> > --- a/qapi-schema.json
> > +++ b/qapi-schema.json
> > @@ -2782,6 +2782,8 @@
> >  # Since: 1.4
> >  ##
> >  { 'type': 'ChardevSocket', 'data': { 'addr'       : 'SocketAddress',
> > +                                     '*tls-cred'  : 'str',
> > +                                     '*acl'       : 'str',
> 
> Need to document these two fields, along with '(since 2.4)' designators.

Ah, ok forgot about that.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 34/34] char: introduce support for TLS encrypted TCP chardev backend
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 34/34] char: introduce support for TLS encrypted TCP chardev backend Daniel P. Berrange
  2015-04-17 18:27   ` Eric Blake
@ 2015-05-04 20:07   ` Kashyap Chamarthy
  2015-05-05 13:49     ` Daniel P. Berrange
  1 sibling, 1 reply; 71+ messages in thread
From: Kashyap Chamarthy @ 2015-05-04 20:07 UTC (permalink / raw)
  To: Daniel P. Berrange
  Cc: Paolo Bonzini, qemu-devel, Stefan Hajnoczi, Gerd Hoffmann

On Fri, Apr 17, 2015 at 03:22:37PM +0100, Daniel P. Berrange wrote:
> This integrates support for QIOChannelTLS object in the TCP
> chardev backend. If the 'tls-cred=NAME' option is passed with
> the '-chardev tcp' argument, then it will setup the chardev
> such that the client is required to establish a TLS handshake
> when connecting. The 'acl' option will further enable the
> creation of a 'char.$ID.tlspeername' ACL which will be used
> to validate the client x509 certificate, if provided.
> 
> A complete invokation to run QEMU as the server for a TLS
> encrypted serial dev might be
> 
>   $ qemu-system-x86_64 \
>       -nodefconfig -nodefaults -device sga -display none \
>       -chardev socket,id=s0,host=127.0.0.1,port=9000,tls-cred=tls0,server \
>       -device isa-serial,chardev=s0 \
>       -object qcrypto-tls-cred,id=tls0,credtype=x509,\
>         endpoint=server,dir=/home/berrange/security/qemutls,verify-peer=off
> 
> To test with the gnutls-cli tool as the client:
> 
>   $ gnutls-cli --priority=NORMAL -p 9000 \
>        --x509cafile=/home/berrange/security/qemutls/ca-cert.pem \
>        127.0.0.1
> 
> If QEMU was told to use 'anon' credential type, then use the
> priority string 'NOMAL:+ANON-DH' with gnutls-cli
> 
> Alternatively, if setting up a chardev to operate as a client,
> then the TLS credentials registered must be for the client
> endpoint. First a TLS server must be setup, which can be done
> with the gnutls-serv tool
> 
>   $ gnutls-serv --priority=NORMAL -p 9000 \
>        --x509cafile=/home/berrange/security/qemutls/ca-cert.pem \
>        --x509certfile=/home/berrange/security/qemutls/server-cert.pem \
>        --x509keyfile=/home/berrange/security/qemutls/server-key.pem
> 
> Then QEMU can connect with
> 
>   $ qemu-system-x86_64 \
>       -nodefconfig -nodefaults -device sga -display none \
>       -chardev socket,id=s0,host=127.0.0.1,port=9000,tls-cred=tls0 \
>       -device isa-serial,chardev=s0 \
>       -object qcrypto-tls-cred,id=tls0,credtype=x509,\
>         endpoint=client,dir=/home/berrange/security/qemutls

I've applied your 'qemu-io-channel-7' branch locally, compiled QEMU and
began to play around.

    $ git describe
    v2.3.0-rc3-42-g5878696

When running QEMU either as server or as client, I notice this error
(further below are the details of how I tested):

    [. . .]
    qemu-system-x86_64: -object qcrypto-tls-cred,id=tls0,credtype=x509,:
    invalid object type: qcrypto-tls-cred


Test with QEMU as client
------------------------

Setup PKI environment[1] , and run a GnuTLS server:

    $ gnutls-serv --priority=NORMAL -p 9000 \
        --x509cafile=/export/security/gnutls/ca-cert.pem \
        --x509certfile=/export/security/gnutls/server-cert.pem \
        --x509keyfile=/export/security/gnutls/server-key.pem
    Set static Diffie-Hellman parameters, consider --dhparams.
    Processed 1 CA certificate(s).
    HTTP Server listening on IPv4 0.0.0.0 port 9000...done
    HTTP Server listening on IPv6 :: port 9000...done

And, connect with QEMU:

    $ /home/kashyapc/build/tls-qemu/x86_64-softmmu/qemu-system-x86_64 \
        -nodefconfig -nodefaults -device sga -display none \
        -chardev socket,id=s0,host=localhost,port=9000,tls-cred=tls0 \
        -device isa-serial,chardev=s0 \
        -object qcrypto-tls-cred,id=tls0,credtype=x509,\
        endpoint=client,dir=/export/security/gnutls
    qemu-system-x86_64: -object qcrypto-tls-cred,id=tls0,credtype=x509,:
    invalid object type: qcrypto-tls-cred


Test with QEMU as server
------------------------

    $ /home/kashyapc/build/tls-qemu/x86_64-softmmu/qemu-system-x86_64 \
        -nodefconfig -nodefaults -device sga -display none \
        -chardev socket,id=s0,host=localhost,port=9000,tls-cred=tls0,server \
        -device isa-serial,chardev=s0 \
        -object qcrypto-tls-cred,id=tls0,credtype=x509,\
        endpoint=server,dir=/export/security/gnutls,verify-peer=off
    qemu-system-x86_64: -object qcrypto-tls-cred,id=tls0,credtype=x509,:
    invalid object type: qcrypto-tls-cred


Am I missing something simple?


Additional notes
----------------

(a) I verified the QEMU CLI for -object is correct by looking at local the
    'qemu-options.hx' file:

    @item -object
    qcrypto-tls-cred,id=@var{id},credtype=@var{type},endpoint=@var{endpoint},
    dir=@var{/path/to/cred/dir},verify-peer=@var{on|off}

(b) Just to ensure that TLS server is setup correctly, I validated it via
    `gnutls-cli`:

    $ gnutls-cli --priority=NORMAL -p 9000 \
        --x509cafile=/export/security/gnutls/ca-cert.pem localhost
    [. . .]
    - Status: The certificate is trusted. 
    - Successfully sent 0 certificate(s) to server.
    - Compression: NULL
    - Options: safe renegotiation,
    - Handshake was completed
    [. . .]

(c) Exact CLI invocatoins of how I created the self-signed CA, server
    certificate including their outputs are noted here[1].

(d) When creating the server certificate request, I used the 'dnsName'
    attribute, and gave its value as "localhost".


[1] https://kashyapc.fedorapeople.org/gnutls-pki-setup.txt
 

-- 
/kashyap

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 34/34] char: introduce support for TLS encrypted TCP chardev backend
  2015-05-04 20:07   ` Kashyap Chamarthy
@ 2015-05-05 13:49     ` Daniel P. Berrange
  2015-05-05 13:53       ` Paolo Bonzini
  2015-05-05 14:54       ` Kashyap Chamarthy
  0 siblings, 2 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-05-05 13:49 UTC (permalink / raw)
  To: Kashyap Chamarthy
  Cc: Paolo Bonzini, qemu-devel, Stefan Hajnoczi, Gerd Hoffmann

On Mon, May 04, 2015 at 10:07:15PM +0200, Kashyap Chamarthy wrote:
> On Fri, Apr 17, 2015 at 03:22:37PM +0100, Daniel P. Berrange wrote:
> > This integrates support for QIOChannelTLS object in the TCP
> > chardev backend. If the 'tls-cred=NAME' option is passed with
> > the '-chardev tcp' argument, then it will setup the chardev
> > such that the client is required to establish a TLS handshake
> > when connecting. The 'acl' option will further enable the
> > creation of a 'char.$ID.tlspeername' ACL which will be used
> > to validate the client x509 certificate, if provided.
> > 
> > A complete invokation to run QEMU as the server for a TLS
> > encrypted serial dev might be
> > 
> >   $ qemu-system-x86_64 \
> >       -nodefconfig -nodefaults -device sga -display none \
> >       -chardev socket,id=s0,host=127.0.0.1,port=9000,tls-cred=tls0,server \
> >       -device isa-serial,chardev=s0 \
> >       -object qcrypto-tls-cred,id=tls0,credtype=x509,\
> >         endpoint=server,dir=/home/berrange/security/qemutls,verify-peer=off
> > 
> > To test with the gnutls-cli tool as the client:
> > 
> >   $ gnutls-cli --priority=NORMAL -p 9000 \
> >        --x509cafile=/home/berrange/security/qemutls/ca-cert.pem \
> >        127.0.0.1
> > 
> > If QEMU was told to use 'anon' credential type, then use the
> > priority string 'NOMAL:+ANON-DH' with gnutls-cli
> > 
> > Alternatively, if setting up a chardev to operate as a client,
> > then the TLS credentials registered must be for the client
> > endpoint. First a TLS server must be setup, which can be done
> > with the gnutls-serv tool
> > 
> >   $ gnutls-serv --priority=NORMAL -p 9000 \
> >        --x509cafile=/home/berrange/security/qemutls/ca-cert.pem \
> >        --x509certfile=/home/berrange/security/qemutls/server-cert.pem \
> >        --x509keyfile=/home/berrange/security/qemutls/server-key.pem
> > 
> > Then QEMU can connect with
> > 
> >   $ qemu-system-x86_64 \
> >       -nodefconfig -nodefaults -device sga -display none \
> >       -chardev socket,id=s0,host=127.0.0.1,port=9000,tls-cred=tls0 \
> >       -device isa-serial,chardev=s0 \
> >       -object qcrypto-tls-cred,id=tls0,credtype=x509,\
> >         endpoint=client,dir=/home/berrange/security/qemutls
> 
> I've applied your 'qemu-io-channel-7' branch locally, compiled QEMU and
> began to play around.
> 
>     $ git describe
>     v2.3.0-rc3-42-g5878696
> 
> When running QEMU either as server or as client, I notice this error
> (further below are the details of how I tested):
> 
>     [. . .]
>     qemu-system-x86_64: -object qcrypto-tls-cred,id=tls0,credtype=x509,:
>     invalid object type: qcrypto-tls-cred

Typo in my commit message - it should end in  '-creds' not '-cred' for
the object type.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 34/34] char: introduce support for TLS encrypted TCP chardev backend
  2015-05-05 13:49     ` Daniel P. Berrange
@ 2015-05-05 13:53       ` Paolo Bonzini
  2015-05-05 13:56         ` Daniel P. Berrange
  2015-05-05 14:54       ` Kashyap Chamarthy
  1 sibling, 1 reply; 71+ messages in thread
From: Paolo Bonzini @ 2015-05-05 13:53 UTC (permalink / raw)
  To: Daniel P. Berrange, Kashyap Chamarthy
  Cc: qemu-devel, Stefan Hajnoczi, Gerd Hoffmann



On 05/05/2015 15:49, Daniel P. Berrange wrote:
>> >     qemu-system-x86_64: -object qcrypto-tls-cred,id=tls0,credtype=x509,:
>> >     invalid object type: qcrypto-tls-cred
> Typo in my commit message - it should end in  '-creds' not '-cred' for
> the object type.

FWIW, I think just "tls-creds" is probably a better name for the -object
option.  "qcrypto" is more of an internal detail.

Paolo

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 34/34] char: introduce support for TLS encrypted TCP chardev backend
  2015-05-05 13:53       ` Paolo Bonzini
@ 2015-05-05 13:56         ` Daniel P. Berrange
  0 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-05-05 13:56 UTC (permalink / raw)
  To: Paolo Bonzini
  Cc: Gerd Hoffmann, qemu-devel, Stefan Hajnoczi, Kashyap Chamarthy

On Tue, May 05, 2015 at 03:53:41PM +0200, Paolo Bonzini wrote:
> 
> 
> On 05/05/2015 15:49, Daniel P. Berrange wrote:
> >> >     qemu-system-x86_64: -object qcrypto-tls-cred,id=tls0,credtype=x509,:
> >> >     invalid object type: qcrypto-tls-cred
> > Typo in my commit message - it should end in  '-creds' not '-cred' for
> > the object type.
> 
> FWIW, I think just "tls-creds" is probably a better name for the -object
> option.  "qcrypto" is more of an internal detail.

Ok, I'll drop the prefix from the name - I guess chance of a namespace
clash is pretty low anyway.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 34/34] char: introduce support for TLS encrypted TCP chardev backend
  2015-05-05 13:49     ` Daniel P. Berrange
  2015-05-05 13:53       ` Paolo Bonzini
@ 2015-05-05 14:54       ` Kashyap Chamarthy
  2015-05-06  8:34         ` Kashyap Chamarthy
  1 sibling, 1 reply; 71+ messages in thread
From: Kashyap Chamarthy @ 2015-05-05 14:54 UTC (permalink / raw)
  To: Daniel P. Berrange
  Cc: Paolo Bonzini, qemu-devel, Stefan Hajnoczi, Gerd Hoffmann

On Tue, May 05, 2015 at 02:49:51PM +0100, Daniel P. Berrange wrote:
> On Mon, May 04, 2015 at 10:07:15PM +0200, Kashyap Chamarthy wrote:
> > On Fri, Apr 17, 2015 at 03:22:37PM +0100, Daniel P. Berrange wrote:
> > > This integrates support for QIOChannelTLS object in the TCP
> > > chardev backend. If the 'tls-cred=NAME' option is passed with
> > > the '-chardev tcp' argument, then it will setup the chardev
> > > such that the client is required to establish a TLS handshake
> > > when connecting. The 'acl' option will further enable the
> > > creation of a 'char.$ID.tlspeername' ACL which will be used
> > > to validate the client x509 certificate, if provided.
> > > 
> > > A complete invokation to run QEMU as the server for a TLS
> > > encrypted serial dev might be
> > > 
> > >   $ qemu-system-x86_64 \
> > >       -nodefconfig -nodefaults -device sga -display none \
> > >       -chardev socket,id=s0,host=127.0.0.1,port=9000,tls-cred=tls0,server \
> > >       -device isa-serial,chardev=s0 \
> > >       -object qcrypto-tls-cred,id=tls0,credtype=x509,\
> > >         endpoint=server,dir=/home/berrange/security/qemutls,verify-peer=off
> > > 
> > > To test with the gnutls-cli tool as the client:
> > > 
> > >   $ gnutls-cli --priority=NORMAL -p 9000 \
> > >        --x509cafile=/home/berrange/security/qemutls/ca-cert.pem \
> > >        127.0.0.1
> > > 
> > > If QEMU was told to use 'anon' credential type, then use the
> > > priority string 'NOMAL:+ANON-DH' with gnutls-cli
> > > 
> > > Alternatively, if setting up a chardev to operate as a client,
> > > then the TLS credentials registered must be for the client
> > > endpoint. First a TLS server must be setup, which can be done
> > > with the gnutls-serv tool
> > > 
> > >   $ gnutls-serv --priority=NORMAL -p 9000 \
> > >        --x509cafile=/home/berrange/security/qemutls/ca-cert.pem \
> > >        --x509certfile=/home/berrange/security/qemutls/server-cert.pem \
> > >        --x509keyfile=/home/berrange/security/qemutls/server-key.pem
> > > 
> > > Then QEMU can connect with
> > > 
> > >   $ qemu-system-x86_64 \
> > >       -nodefconfig -nodefaults -device sga -display none \
> > >       -chardev socket,id=s0,host=127.0.0.1,port=9000,tls-cred=tls0 \
> > >       -device isa-serial,chardev=s0 \
> > >       -object qcrypto-tls-cred,id=tls0,credtype=x509,\
> > >         endpoint=client,dir=/home/berrange/security/qemutls
> > 
> > I've applied your 'qemu-io-channel-7' branch locally, compiled QEMU and
> > began to play around.
> > 
> >     $ git describe
> >     v2.3.0-rc3-42-g5878696
> > 
> > When running QEMU either as server or as client, I notice this error
> > (further below are the details of how I tested):
> > 
> >     [. . .]
> >     qemu-system-x86_64: -object qcrypto-tls-cred,id=tls0,credtype=x509,:
> >     invalid object type: qcrypto-tls-cred
> 
> Typo in my commit message - it should end in  '-creds' not '-cred' for
> the object type.

Yep, that fixed it. I should have looked deeper -- your example in
include/crypto/tlscreds.h has the correct syntax and also includes a
QMP variant. Just to note, there seems to be three instances of this
typo in qemu-options.hx (found via `git grep qcrypto-tls-cred`).

While running QEMU as TLS server, the TLS handshake completes
successfully when connected via `gnutls-cli`.

However, when using QEMU as client to connect to an existing GnuTLS
server, I notice a segmentation fault:

  $ /home/kashyapc/build/tls-qemu/x86_64-softmmu/qemu-system-x86_64 \
      -nodefconfig -nodefaults -device sga -display none \
      -chardev socket,id=s0,host=localhost,port=9000,tls-cred=tls0 \
      -device isa-serial,chardev=s0 \
      -object qcrypto-tls-creds,id=tls0,credtype=x509,endpoint=client,dir=/export/security/gnutls
  Segmentation fault (core dumped)


-- 
/kashyap

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 34/34] char: introduce support for TLS encrypted TCP chardev backend
  2015-05-05 14:54       ` Kashyap Chamarthy
@ 2015-05-06  8:34         ` Kashyap Chamarthy
  2015-05-06 10:18           ` Daniel P. Berrange
  0 siblings, 1 reply; 71+ messages in thread
From: Kashyap Chamarthy @ 2015-05-06  8:34 UTC (permalink / raw)
  To: Daniel P. Berrange
  Cc: Paolo Bonzini, qemu-devel, Stefan Hajnoczi, Gerd Hoffmann

On Tue, May 05, 2015 at 04:54:44PM +0200, Kashyap Chamarthy wrote:

[. . .]

> While running QEMU as TLS server, the TLS handshake completes
> successfully when connected via `gnutls-cli`.
> 
> However, when using QEMU as client to connect to an existing GnuTLS
> server, I notice a segmentation fault:
> 
>   $ /home/kashyapc/build/tls-qemu/x86_64-softmmu/qemu-system-x86_64 \
>       -nodefconfig -nodefaults -device sga -display none \
>       -chardev socket,id=s0,host=localhost,port=9000,tls-cred=tls0 \
>       -device isa-serial,chardev=s0 \
>       -object qcrypto-tls-creds,id=tls0,credtype=x509,endpoint=client,dir=/export/security/gnutls
>   Segmentation fault (core dumped)

Some debugging with `gdb` below.

QEMU was built with:

    ./configure --target-list=x86_64-softmmu --enable-debug
    make -j4

Stack traces:

$ gdb /home/kashyapc/build/tls-qemu/x86_64-softmmu/qemu-system-x86_64
[. . .]
(gdb) run  -nodefconfig -nodefaults -device sga -display none     -chardev socket,id=s0,host=localhost,port=9000,tls-cred=tls0     -device isa-serial,chardev=s0     -object qcrypto-tls-creds,id=tls0,credtype=x509,endpoint=client,dir=/export/security/gnutls
Starting program: /home/kashyapc/build/tls-qemu/x86_64-softmmu/qemu-system-x86_64 -nodefconfig -nodefaults -device sga -display none     -chardev socket,id=s0,host=localhost,port=9000,tls-cred=tls0     -device isa-serial,chardev=s0     -object qcrypto-tls-creds,id=tls0,credtype=x509,endpoint=client,dir=/export/security/gnutls
[. . .]
Program received signal SIGSEGV, Segmentation fault.
__strstr_sse2_unaligned () at ../sysdeps/x86_64/multiarch/strstr-sse2-unaligned.S:40
40              movdqu  (%rdi), %xmm3
(gdb) thread apply all bt full

Thread 2 (Thread 0x7fffe4fcc700 (LWP 5393)):
#0  0x00007ffff6bce8fd in nanosleep () at ../sysdeps/unix/syscall-template.S:81
#1  0x00007ffff64f1de8 in g_usleep () at /lib64/libglib-2.0.so.0
#2  0x00005555559d32d7 in call_rcu_thread (opaque=0x0) at /home/kashyapc/tinker-space/qemu/util/rcu.c:228
        tries = 0
        n = 0
        node = 0x7ffff7fd19a0
#3  0x00007ffff6bc652a in start_thread (arg=0x7fffe4fcc700) at pthread_create.c:310
        __res = <optimized out>
        pd = 0x7fffe4fcc700
        now = <optimized out>
        unwind_buf = 
              {cancel_jmp_buf = {{jmp_buf = {140737035159296, 3180389637749088242, 140737488345857, 4096, 140737035159296, 140737035160000, -3180444589616128014, -3180404459381186574}, mask_was_saved = 0}}, priv = {pad = {0x0, 0x0, 0x0, 0x0}, data = {prev = 0x0, cleanup = 0x0, canceltype = 0}}}
        not_first_call = <optimized out>
        pagesize_m1 = <optimized out>
        sp = <optimized out>
        freesize = <optimized out>
#4  0x00007fffeea0979d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109

---Type <return> to continue, or q <return> to quit---
Thread 1 (Thread 0x7ffff7f89bc0 (LWP 5389)):
#0  0x00007fffee9ae6dd in __strstr_sse2_unaligned () at ../sysdeps/x86_64/multiarch/strstr-sse2-unaligned.S:40
#1  0x00007ffff1c6b370 in _gnutls_url_is_known () at /lib64/libgnutls.so.28
#2  0x00007ffff1c6b3d9 in gnutls_certificate_set_x509_key_file2 () at /lib64/libgnutls.so.28
#3  0x00005555559aba85 in qcrypto_tls_creds_load_x509 (creds=0x55555639ac60, errp=0x7fffffffd8d8) at /home/kashyapc/tinker-space/qemu/crypto/tlscreds.c:728
        cacert = 0x55555639a8c0 "/export/security/gnutls/ca-cert.pem"
        cacrl = 0x0
        cert = 0x0
        key = 0x0
        dhparams = 0x0
        ret = 1
        rv = -1
#4  0x00005555559abdb2 in qcrypto_tls_creds_load (creds=0x55555639ac60, errp=0x7fffffffd8d8) at /home/kashyapc/tinker-space/qemu/crypto/tlscreds.c:820
#5  0x00005555559abf30 in qcrypto_tls_creds_prop_set_loaded (obj=0x55555639ac60, value=true, errp=0x7fffffffd8d8) at /home/kashyapc/tinker-space/qemu/crypto/tlscreds.c:888
        creds = 0x55555639ac60
        __func__ = "qcrypto_tls_creds_prop_set_loaded"
#6  0x00005555558cec1c in property_set_bool (obj=0x55555639ac60, v=0x55555639b4d0, opaque=0x55555639ad40, name=0x555555a59695 "loaded", errp=0x7fffffffd8d8)
    at /home/kashyapc/tinker-space/qemu/qom/object.c:1600
        prop = 0x55555639ad40
        value = true
        local_err = 0x0
---Type <return> to continue, or q <return> to quit---
#7  0x00005555558cd485 in object_property_set (obj=0x55555639ac60, v=0x55555639b4d0, name=0x555555a59695 "loaded", errp=0x7fffffffd8d8) at /home/kashyapc/tinker-space/qemu/qom/object.c:901
        prop = 0x55555639ad60
#8  0x00005555558cfa47 in object_property_set_qobject (obj=0x55555639ac60, value=0x55555639b200, name=0x555555a59695 "loaded", errp=0x7fffffffd8d8)
    at /home/kashyapc/tinker-space/qemu/qom/qom-qobject.c:24
        mi = 0x55555639b4d0
#9  0x00005555558cd6f4 in object_property_set_bool (obj=0x55555639ac60, value=true, name=0x555555a59695 "loaded", errp=0x7fffffffd8d8) at /home/kashyapc/tinker-space/qemu/qom/object.c:969
        qbool = 0x55555639b200
#10 0x00005555559ac2e5 in qcrypto_tls_creds_complete (uc=0x55555639ac60, errp=0x7fffffffd8d8) at /home/kashyapc/tinker-space/qemu/crypto/tlscreds.c:1018
#11 0x00005555558d0899 in user_creatable_complete (obj=0x55555639ac60, errp=0x7fffffffd8d8) at /home/kashyapc/tinker-space/qemu/qom/object_interfaces.c:17
        ucc = 0x5555563702f0
        uc = 0x55555639ac60
        __func__ = "user_creatable_complete"
#12 0x0000555555750201 in object_add (type=0x55555639a8f0 "qcrypto-tls-creds", id=0x55555639a850 "tls0", qdict=0x5555563997b0, v=0x5555563996a0, errp=0x7fffffffd920)
    at /home/kashyapc/tinker-space/qemu/qmp.c:659
        obj = 0x55555639ac60
        klass = 0x555556370050
        e = 0x0
        local_err = 0x0
#13 0x0000555555736a2d in object_create (opts=0x55555638a7e0, opaque=0x55555573684e <object_create_phase1>) at /home/kashyapc/tinker-space/qemu/vl.c:2644
        err = 0x0
        type = 0x55555639a8f0 "qcrypto-tls-creds"
---Type <return> to continue, or q <return> to quit---
        id = 0x55555639a850 "tls0"
        dummy = 0x55555639aaf0
        ov = 0x5555563996a0
        pdict = 0x5555563997b0
        type_predicate = 0x55555573684e <object_create_phase1>
#14 0x00005555559d08e0 in qemu_opts_foreach (list=0x555555e12ee0 <qemu_object_opts>, func=0x5555557368aa <object_create>, opaque=0x55555573684e <object_create_phase1>, abort_on_failure=0)
    at /home/kashyapc/tinker-space/qemu/util/qemu-option.c:1059
        loc = {kind = LOC_CMDLINE, num = 2, ptr = 0x7fffffffde10, prev = 0x555556315300 <std_loc>}
        opts = 0x55555638a7e0
        rc = 0
#15 0x000055555573a273 in main (argc=13, argv=0x7fffffffddb8, envp=0x7fffffffde28) at /home/kashyapc/tinker-space/qemu/vl.c:4039
        i = 21845
        snapshot = 0
        linux_boot = 3
        initrd_filename = 0xffff800000002441 <error: Cannot access memory at address 0xffff800000002441>
        kernel_filename = 0xffffffffffffffff <error: Cannot access memory at address 0xffffffffffffffff>
        kernel_cmdline = 0x555556345060 "\241x\244UUU"
        boot_order = 0x0
        boot_once = 0x0
        ds = 0x7fffffffdbbf
        cyls = 0
---Type <return> to continue, or q <return> to quit---
        heads = 0
        secs = 0
        translation = 0
        hda_opts = 0x0
        opts = 0x55555638aa50
        machine_opts = 0xffffffffffffffff
        icount_opts = 0x0
        olist = 0x0
        optind = 13
        optarg = 0x0
        loadvm = 0x0
        machine_class = 0x55555637ac70
        cpu_model = 0x0
        vga_model = 0x0
        qtest_chrdev = 0x0
        qtest_log = 0x0
        pid_file = 0x0
        incoming = 0x0
        show_vnc_port = 0
        defconfig = false
        userconfig = true
---Type <return> to continue, or q <return> to quit---
        log_mask = 0x0
        log_file = 0x0
        mem_trace = 
    {malloc = 0x5555557366c1 <malloc_and_trace>, realloc = 0x5555557366f6 <realloc_and_trace>, free = 0x55555573673a <free_and_trace>, calloc = 0x0, try_malloc = 0x0, try_realloc = 0x0}
        trace_events = 0x0
        trace_file = 0x0
        maxram_size = 134217728
        ram_slots = 0
        vmstate_dump_file = 0x0
        main_loop_err = 0x0
        err = 0x0
        __func__ = "main"


-- 
/kashyap

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 34/34] char: introduce support for TLS encrypted TCP chardev backend
  2015-05-06  8:34         ` Kashyap Chamarthy
@ 2015-05-06 10:18           ` Daniel P. Berrange
  2015-05-06 11:38             ` Kashyap Chamarthy
  0 siblings, 1 reply; 71+ messages in thread
From: Daniel P. Berrange @ 2015-05-06 10:18 UTC (permalink / raw)
  To: Kashyap Chamarthy
  Cc: Paolo Bonzini, qemu-devel, Stefan Hajnoczi, Gerd Hoffmann

On Wed, May 06, 2015 at 10:34:06AM +0200, Kashyap Chamarthy wrote:
> On Tue, May 05, 2015 at 04:54:44PM +0200, Kashyap Chamarthy wrote:
> 
> [. . .]
> 
> > While running QEMU as TLS server, the TLS handshake completes
> > successfully when connected via `gnutls-cli`.
> > 
> > However, when using QEMU as client to connect to an existing GnuTLS
> > server, I notice a segmentation fault:
> > 
> >   $ /home/kashyapc/build/tls-qemu/x86_64-softmmu/qemu-system-x86_64 \
> >       -nodefconfig -nodefaults -device sga -display none \
> >       -chardev socket,id=s0,host=localhost,port=9000,tls-cred=tls0 \
> >       -device isa-serial,chardev=s0 \
> >       -object qcrypto-tls-creds,id=tls0,credtype=x509,endpoint=client,dir=/export/security/gnutls
> >   Segmentation fault (core dumped)
> 
> Some debugging with `gdb` below.
> 
> QEMU was built with:
> 
>     ./configure --target-list=x86_64-softmmu --enable-debug
>     make -j4
> 
> Stack traces:
> 
> $ gdb /home/kashyapc/build/tls-qemu/x86_64-softmmu/qemu-system-x86_64

> #2  0x00007ffff1c6b3d9 in gnutls_certificate_set_x509_key_file2 () at /lib64/libgnutls.so.28
> #3  0x00005555559aba85 in qcrypto_tls_creds_load_x509 (creds=0x55555639ac60, errp=0x7fffffffd8d8) at /home/kashyapc/tinker-space/qemu/crypto/tlscreds.c:728
>         cacert = 0x55555639a8c0 "/export/security/gnutls/ca-cert.pem"
>         cacrl = 0x0
>         cert = 0x0
>         key = 0x0
>         dhparams = 0x0
>         ret = 1
>         rv = -1

Ah, with QEMU running in client mode, the client cert + key are optional. In this
case you've not provided them (cert & key are 0x0 ie NULL). We are then mistakenly
calling gnutls_certificate_set_x509_key_file2 - if I simply skip that I'll avoid
the crash. Thanks for testing this - I'll add a test case to validate this scenario
too

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 34/34] char: introduce support for TLS encrypted TCP chardev backend
  2015-05-06 10:18           ` Daniel P. Berrange
@ 2015-05-06 11:38             ` Kashyap Chamarthy
  0 siblings, 0 replies; 71+ messages in thread
From: Kashyap Chamarthy @ 2015-05-06 11:38 UTC (permalink / raw)
  To: Daniel P. Berrange
  Cc: Paolo Bonzini, qemu-devel, Stefan Hajnoczi, Gerd Hoffmann

On Wed, May 06, 2015 at 11:18:23AM +0100, Daniel P. Berrange wrote:
> On Wed, May 06, 2015 at 10:34:06AM +0200, Kashyap Chamarthy wrote:
> > On Tue, May 05, 2015 at 04:54:44PM +0200, Kashyap Chamarthy wrote:
> > 
> > [. . .]
> > 
> > > While running QEMU as TLS server, the TLS handshake completes
> > > successfully when connected via `gnutls-cli`.
> > > 
> > > However, when using QEMU as client to connect to an existing GnuTLS
> > > server, I notice a segmentation fault:
> > > 
> > >   $ /home/kashyapc/build/tls-qemu/x86_64-softmmu/qemu-system-x86_64 \
> > >       -nodefconfig -nodefaults -device sga -display none \
> > >       -chardev socket,id=s0,host=localhost,port=9000,tls-cred=tls0 \
> > >       -device isa-serial,chardev=s0 \
> > >       -object qcrypto-tls-creds,id=tls0,credtype=x509,endpoint=client,dir=/export/security/gnutls
> > >   Segmentation fault (core dumped)
> > 
> > Some debugging with `gdb` below.
> > 
> > QEMU was built with:
> > 
> >     ./configure --target-list=x86_64-softmmu --enable-debug
> >     make -j4
> > 
> > Stack traces:
> > 
> > $ gdb /home/kashyapc/build/tls-qemu/x86_64-softmmu/qemu-system-x86_64
> 
> > #2  0x00007ffff1c6b3d9 in gnutls_certificate_set_x509_key_file2 () at /lib64/libgnutls.so.28
> > #3  0x00005555559aba85 in qcrypto_tls_creds_load_x509 (creds=0x55555639ac60, errp=0x7fffffffd8d8) at /home/kashyapc/tinker-space/qemu/crypto/tlscreds.c:728
> >         cacert = 0x55555639a8c0 "/export/security/gnutls/ca-cert.pem"
> >         cacrl = 0x0
> >         cert = 0x0
> >         key = 0x0
> >         dhparams = 0x0
> >         ret = 1
> >         rv = -1
> 
> Ah, with QEMU running in client mode, the client cert + key are
> optional. In this case you've not provided them (cert & key are 0x0 ie
> NULL).

Yep, I generated a client-key.pem, client-cert.pem and placed them in
the same gnutls directory where the ca* and server* files are located.
Indeed the TLS handshake completes succesfully (tested with the same
QEMU CLI as above placed in a script):

  $ ./chardev-backend-qemu-as-tls-client.sh 
  Handshake still running 2
  Handshake still running 2
  Handshake compelte session=0x7f8bdda6f4c0

On the GnuTLS server:

  [. . .]
  * Accepted connection from IPv4 127.0.0.1 port 51353 on Wed May  6 13:19:10 2015
  - Description: (TLS1.2)-(ECDHE-RSA-SECP256R1)-(AES-128-CBC)-(SHA1)
  [. . .]
  - Server has requested a certificate.
  - Certificate type: X.509
  - Got a certificate list of 1 certificates.
  [. . .]
  - Version: TLS1.2
  - Key Exchange: ECDHE-RSA
  - Server Signature: RSA-SHA256
  - Client Signature: RSA-SHA256
  - Compression: NULL
  - Options: safe renegotiation,
  - Channel binding 'tls-unique': 7f4ae1e0dc02dbad602a9c27

> We are then mistakenly calling gnutls_certificate_set_x509_key_file2 -
> if I simply skip that I'll avoid the crash. Thanks for testing this -
> I'll add a test case to validate this scenario too

-- 
/kashyap

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 08/34] crypto: introduce new module for computing hash digests
  2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 08/34] crypto: introduce new module for computing hash digests Daniel P. Berrange
@ 2015-05-13 17:04   ` Daniel P. Berrange
  2015-05-13 17:12     ` Paolo Bonzini
  0 siblings, 1 reply; 71+ messages in thread
From: Daniel P. Berrange @ 2015-05-13 17:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: Paolo Bonzini, Gerd Hoffmann, Stefan Hajnoczi

On Fri, Apr 17, 2015 at 03:22:11PM +0100, Daniel P. Berrange wrote:
> Introduce a new crypto/ directory that will (eventually) contain
> all the cryptographic related code. This initially defines a
> wrapper for initializing gnutls and for computing hashes with
> gnutls. The former ensures that gnutls is guaranteed to be
> initialized exactly once in QEMU regardless of CLI args. The
> block quorum code currently fails to initialize gnutls so it
> only works by luck, if VNC server TLS is not requested. The
> hash APIs avoids the need to litter the rest of the code with
> preprocessor checks and simplifies callers by allocating the
> correct amount of memory for the requested hash.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  Makefile.objs            |   1 +
>  configure                |  46 +++++++++++
>  crypto/Makefile.objs     |   2 +
>  crypto/hash.c            | 202 +++++++++++++++++++++++++++++++++++++++++++++
>  crypto/init.c            |  62 ++++++++++++++
>  include/crypto/hash.h    | 189 ++++++++++++++++++++++++++++++++++++++++++
>  include/crypto/init.h    |  29 +++++++
>  tests/.gitignore         |   1 +
>  tests/Makefile           |   2 +
>  tests/test-crypto-hash.c | 209 +++++++++++++++++++++++++++++++++++++++++++++++
>  vl.c                     |   8 ++
>  11 files changed, 751 insertions(+)

> diff --git a/crypto/init.c b/crypto/init.c
> new file mode 100644
> index 0000000..8fd66d4
> --- /dev/null
> +++ b/crypto/init.c

> +
> +#include "crypto/init.h"
> +
> +#include <glib/gi18n.h>
> +
> +#ifdef CONFIG_GNUTLS
> +#include <gnutls/gnutls.h>
> +#include <gnutls/crypto.h>
> +
> +/* #define DEBUG_GNUTLS */
> +
> +#ifdef DEBUG_GNUTLS
> +static void qcrypto_gnutls_log(int level, const char *str)
> +{
> +    fprintf(stderr, "%d: %s", level, str);
> +}
> +#endif
> +
> +int qcrypto_init(Error **errp)
> +{
> +    int ret;
> +    ret = gnutls_global_init();
> +    if (ret < 0) {
> +        error_setg(errp,
> +                   _("Unable to initialize GNUTLS library: %s"),
> +                   gnutls_strerror(ret));
> +        return -1;
> +    }
> +#ifdef DEBUG_GNUTLS
> +    gnutls_global_set_log_level(10);
> +    gnutls_global_set_log_function(qcrypto_gnutls_log);
> +#endif
> +    return 0;
> +}
> +
> +#else /* ! CONFIG_GNUTLS */
> +
> +int qcrypto_init(Error **errp G_GNUC_UNUSED)
> +{
> +    return 0;
> +}
> +
> +#endif /* ! CONFIG_GNUTLS */

[snip]

> diff --git a/vl.c b/vl.c
> index 8aac4ee..6902253 100644
> --- a/vl.c
> +++ b/vl.c
> @@ -119,6 +119,7 @@ int main(int argc, char **argv)
>  #include "qapi/opts-visitor.h"
>  #include "qom/object_interfaces.h"
>  #include "qapi-event.h"
> +#include "crypto/init.h"
>  
>  #define DEFAULT_RAM_SIZE 128
>  
> @@ -2787,6 +2788,7 @@ int main(int argc, char **argv, char **envp)
>      uint64_t ram_slots = 0;
>      FILE *vmstate_dump_file = NULL;
>      Error *main_loop_err = NULL;
> +    Error *err = NULL;
>  
>      qemu_init_cpu_loop();
>      qemu_mutex_lock_iothread();
> @@ -2829,6 +2831,12 @@ int main(int argc, char **argv, char **envp)
>  
>      runstate_init();
>  
> +    if (qcrypto_init(&err) < 0) {
> +        fprintf(stderr, "Cannot initialize crypto: %s\n",
> +                error_get_pretty(err));
> +        error_free(err);
> +        exit(1);
> +    }
>      rtc_clock = QEMU_CLOCK_HOST;
>  
>      QLIST_INIT (&vm_change_state_head);

I created the 'qcrypto_init()' method in this patch so that we have
a single clear place to initialize gnutls - currently we are doing
initialization only in VNC when TLS is requested. This won't fly
when more areas of the code will use GNUTLS APIs.

It occurs to me though that this patch is either wrong or incomplete,
as I'm only calling qcrypto_init() from vl.c. I would also need to
ensure it is invoked from qemu-io, qemu-img, qemu-nbd, and all the
test suite programs that use libqemutil.la too.

I'm thinking perhaps a better approach could be for the crypto related
APIs to call qcrypto_init() on an as-needed basis. The downside would
be that this could delay the point at which the user sees a gnutls
initialization failure to only after QEMU has been running for a while,
instead of being upfront at startup. The plus side is obviously that
we'd not need to update every binary program main() method.

I notice though that QEMU does not make use of pthread_once() for
global initializers. Is there any particular reason for this ? With
this crypto code it is not safe to rely on being single threaded,
since the crypto code can be invoked from I/O threads as well as
the main event loop. So ideally I would use a pthread_once() instead
of having a static 'bool is_initialized' protected by a pthread_mutex

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 08/34] crypto: introduce new module for computing hash digests
  2015-05-13 17:04   ` Daniel P. Berrange
@ 2015-05-13 17:12     ` Paolo Bonzini
  2015-05-13 17:21       ` Daniel P. Berrange
  0 siblings, 1 reply; 71+ messages in thread
From: Paolo Bonzini @ 2015-05-13 17:12 UTC (permalink / raw)
  To: Daniel P. Berrange, qemu-devel; +Cc: Gerd Hoffmann, Stefan Hajnoczi



On 13/05/2015 19:04, Daniel P. Berrange wrote:
> I'm thinking perhaps a better approach could be for the crypto related
> APIs to call qcrypto_init() on an as-needed basis. The downside would
> be that this could delay the point at which the user sees a gnutls
> initialization failure to only after QEMU has been running for a while,
> instead of being upfront at startup. The plus side is obviously that
> we'd not need to update every binary program main() method.
> 
> I notice though that QEMU does not make use of pthread_once() for
> global initializers. Is there any particular reason for this ? With
> this crypto code it is not safe to rely on being single threaded,
> since the crypto code can be invoked from I/O threads as well as
> the main event loop. So ideally I would use a pthread_once() instead
> of having a static 'bool is_initialized' protected by a pthread_mutex

Yes---though you'd use a GOnce instead for portability.

In fact you would likely keep both, and use a GOnce from qcrypto_init.
Then you can always call qcrypto_init from main if that helps making
early failures clearer.

Paolo

^ permalink raw reply	[flat|nested] 71+ messages in thread

* Re: [Qemu-devel] [PATCH v1 RFC 08/34] crypto: introduce new module for computing hash digests
  2015-05-13 17:12     ` Paolo Bonzini
@ 2015-05-13 17:21       ` Daniel P. Berrange
  0 siblings, 0 replies; 71+ messages in thread
From: Daniel P. Berrange @ 2015-05-13 17:21 UTC (permalink / raw)
  To: Paolo Bonzini; +Cc: qemu-devel, Stefan Hajnoczi, Gerd Hoffmann

On Wed, May 13, 2015 at 07:12:32PM +0200, Paolo Bonzini wrote:
> 
> 
> On 13/05/2015 19:04, Daniel P. Berrange wrote:
> > I'm thinking perhaps a better approach could be for the crypto related
> > APIs to call qcrypto_init() on an as-needed basis. The downside would
> > be that this could delay the point at which the user sees a gnutls
> > initialization failure to only after QEMU has been running for a while,
> > instead of being upfront at startup. The plus side is obviously that
> > we'd not need to update every binary program main() method.
> > 
> > I notice though that QEMU does not make use of pthread_once() for
> > global initializers. Is there any particular reason for this ? With
> > this crypto code it is not safe to rely on being single threaded,
> > since the crypto code can be invoked from I/O threads as well as
> > the main event loop. So ideally I would use a pthread_once() instead
> > of having a static 'bool is_initialized' protected by a pthread_mutex
> 
> Yes---though you'd use a GOnce instead for portability.
> 
> In fact you would likely keep both, and use a GOnce from qcrypto_init.
> Then you can always call qcrypto_init from main if that helps making
> early failures clearer.

Ok, I'll go with GOnce and keep an init call in vl.c for clear error
reporting.

Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|

^ permalink raw reply	[flat|nested] 71+ messages in thread

end of thread, other threads:[~2015-05-13 17:21 UTC | newest]

Thread overview: 71+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-04-17 14:22 [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 01/34] ui: remove check for failure of qemu_acl_init() Daniel P. Berrange
2015-04-17 15:56   ` Eric Blake
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 02/34] qom: document user creatable object types in help text Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 03/34] qom: create objects in two phases Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 04/34] qom: add object_new_propv / object_new_proplist constructors Daniel P. Berrange
2015-04-17 14:55   ` Paolo Bonzini
2015-04-17 15:16     ` Daniel P. Berrange
2015-04-17 16:11   ` Eric Blake
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 05/34] qom: make enum string tables const-correct Daniel P. Berrange
2015-04-17 14:56   ` Paolo Bonzini
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 06/34] qom: add a object_property_add_enum helper method Daniel P. Berrange
2015-04-17 14:56   ` Paolo Bonzini
2015-04-17 15:01     ` Paolo Bonzini
2015-04-17 15:11       ` Daniel P. Berrange
2015-04-17 15:19         ` Paolo Bonzini
2015-04-17 15:22           ` Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 07/34] qom: don't pass string table to object_get_enum method Daniel P. Berrange
2015-04-17 15:05   ` Paolo Bonzini
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 08/34] crypto: introduce new module for computing hash digests Daniel P. Berrange
2015-05-13 17:04   ` Daniel P. Berrange
2015-05-13 17:12     ` Paolo Bonzini
2015-05-13 17:21       ` Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 09/34] crypto: move built-in AES implementation into crypto/ Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 10/34] crypto: move built-in D3DES " Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 11/34] crypto: introduce generic cipher API & built-in implementation Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 12/34] crypto: add a gcrypt cipher implementation Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 13/34] crypto: add a nettle " Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 14/34] crypto: introduce new module for handling TLS credentials Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 15/34] crypto: add sanity checking of " Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 16/34] crypto: introduce new module for handling TLS sessions Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 17/34] block: convert quorum blockdrv to use crypto APIs Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 18/34] ui: convert VNC websockets " Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 19/34] block: convert qcow/qcow2 to use generic cipher API Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 20/34] ui: convert VNC " Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 21/34] io: add abstract QIOChannel classes Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 22/34] io: add helper module for creating watches on UNIX FDs Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 23/34] io: add QIOChannelSocket class Daniel P. Berrange
2015-04-17 15:28   ` Paolo Bonzini
2015-04-17 15:52     ` Daniel P. Berrange
2015-04-17 16:00       ` Paolo Bonzini
2015-04-20  7:18   ` Gerd Hoffmann
2015-04-23 12:31     ` Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 24/34] io: add QIOChannelFile class Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 25/34] io: add QIOTask class for async operations Daniel P. Berrange
2015-04-17 15:16   ` Paolo Bonzini
2015-04-17 15:49     ` Daniel P. Berrange
2015-04-17 15:57       ` Paolo Bonzini
2015-04-17 16:11         ` Daniel P. Berrange
2015-04-17 17:06           ` Paolo Bonzini
2015-04-17 17:38             ` Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 26/34] io: add QIOChannelTLS class Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 27/34] io: pull Buffer code out of VNC module Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 28/34] io: add QIOChannelWebsock class Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 29/34] ui: convert VNC server to use QEMUIOChannelSocket classes Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 30/34] ui: convert VNC server to use QIOChannelTLS Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 31/34] ui: convert VNC server to use QIOChannelWebsock Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 32/34] char: convert from GIOChannel to QIOChannel Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 33/34] char: don't assume telnet initialization will not block Daniel P. Berrange
2015-04-17 14:22 ` [Qemu-devel] [PATCH v1 RFC 34/34] char: introduce support for TLS encrypted TCP chardev backend Daniel P. Berrange
2015-04-17 18:27   ` Eric Blake
2015-04-23 12:32     ` Daniel P. Berrange
2015-05-04 20:07   ` Kashyap Chamarthy
2015-05-05 13:49     ` Daniel P. Berrange
2015-05-05 13:53       ` Paolo Bonzini
2015-05-05 13:56         ` Daniel P. Berrange
2015-05-05 14:54       ` Kashyap Chamarthy
2015-05-06  8:34         ` Kashyap Chamarthy
2015-05-06 10:18           ` Daniel P. Berrange
2015-05-06 11:38             ` Kashyap Chamarthy
2015-04-23 12:28 ` [Qemu-devel] [PATCH v1 RFC 00/34] Generic support for TLS protocol & I/O channels Stefan Hajnoczi

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.