All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Daniel P. Berrange" <berrange@redhat.com>
To: qemu-devel@nongnu.org
Cc: Juan Quintela <quintela@redhat.com>,
	"Dr. David Alan Gilbert" <dgilbert@redhat.com>,
	Gerd Hoffmann <kraxel@redhat.com>,
	Amit Shah <amit.shah@redhat.com>,
	Paolo Bonzini <pbonzini@redhat.com>
Subject: [Qemu-devel] [PATCH FYI 10/46] io: add QIOChannelSocket class
Date: Thu,  3 Sep 2015 16:38:52 +0100	[thread overview]
Message-ID: <1441294768-8712-11-git-send-email-berrange@redhat.com> (raw)
In-Reply-To: <1441294768-8712-1-git-send-email-berrange@redhat.com>

Implement a QIOChannel subclass that supports sockets I/O.
The implementation is able to manage a single socket file
descriptor, whether a TCP/UNIX listener, TCP/UNIX connection,
or a UDP datagram. It provides APIs which can listen and
connect either asynchronously or synchronously. Since there
is no asynchronous DNS lookup API available, it uses the
QIOTask helper for spawning a background thread to ensure
non-blocking operation.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 configure                      |  11 +
 include/io/channel-socket.h    | 297 +++++++++++++++++
 io/Makefile.objs               |   1 +
 io/channel-socket.c            | 718 +++++++++++++++++++++++++++++++++++++++++
 scripts/create_config          |   9 +
 tests/.gitignore               |   1 +
 tests/Makefile                 |   3 +
 tests/io-channel-helpers.c     | 222 +++++++++++++
 tests/io-channel-helpers.h     |  30 ++
 tests/test-io-channel-socket.c | 349 ++++++++++++++++++++
 10 files changed, 1641 insertions(+)
 create mode 100644 include/io/channel-socket.h
 create mode 100644 io/channel-socket.c
 create mode 100644 tests/io-channel-helpers.c
 create mode 100644 tests/io-channel-helpers.h
 create mode 100644 tests/test-io-channel-socket.c

diff --git a/configure b/configure
index 3dbb18f..f53adcd 100755
--- a/configure
+++ b/configure
@@ -2259,6 +2259,14 @@ fi
 
 
 ##########################################
+# getifaddrs (for tests/test-io-channel-socket )
+
+have_ifaddrs_h=yes
+if ! check_include "ifaddrs.h" ; then
+  have_ifaddrs_h=no
+fi
+
+##########################################
 # VTE probe
 
 if test "$vte" != "no"; then
@@ -4901,6 +4909,9 @@ fi
 if test "$tasn1" = "yes" ; then
   echo "CONFIG_TASN1=y" >> $config_host_mak
 fi
+if test "$have_ifaddrs_h" = "yes" ; then
+    echo "HAVE_IFADDRS_H=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/include/io/channel-socket.h b/include/io/channel-socket.h
new file mode 100644
index 0000000..2d1e86a
--- /dev/null
+++ b/include/io/channel-socket.h
@@ -0,0 +1,297 @@
+/*
+ * 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 "io/task.h"
+#include "qemu/sockets.h"
+
+#define TYPE_QIO_CHANNEL_SOCKET "qio-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:
+ *
+ * Create a channel for performing I/O on a socket
+ * connection, that is initially closed. After
+ * creating the socket, it must be setup as a client
+ * connection or server.
+ *
+ * Returns: the socket channel object
+ */
+QIOChannelSocket *
+qio_channel_socket_new(void);
+
+/**
+ * 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_connect_sync:
+ * @ioc: the socket channel object
+ * @addr: the address to connect to
+ * @errp: pointer to an uninitialized error object
+ *
+ * Attempt to connect to the address @addr. This method
+ * will run in the foreground so the caller will not regain
+ * execution control until the connection is established or
+ * an error occurs.
+ */
+int qio_channel_socket_connect_sync(QIOChannelSocket *ioc,
+                                    SocketAddress *addr,
+                                    Error **errp);
+
+/**
+ * qio_channel_socket_connect_async:
+ * @ioc: the socket channel object
+ * @addr: the address to connect to
+ * @callback: the function to invoke on completion
+ * @opaque: user data to pass to @callback
+ * @destroy: the function to free @opaque
+ *
+ * Attempt to connect to the address @addr. This method
+ * will run in the background so the caller will regain
+ * execution control immediately. The function @callback
+ * will be invoked on completion or failure.
+ */
+void qio_channel_socket_connect_async(QIOChannelSocket *ioc,
+                                      SocketAddress *addr,
+                                      QIOTaskFunc callback,
+                                      gpointer opaque,
+                                      GDestroyNotify destroy);
+
+
+/**
+ * qio_channel_socket_listen_sync:
+ * @ioc: the socket channel object
+ * @addr: the address to listen to
+ * @errp: pointer to an uninitialized error object
+ *
+ * Attempt to listen to the address @addr. This method
+ * will run in the foreground so the caller will not regain
+ * execution control until the connection is established or
+ * an error occurs.
+ */
+int qio_channel_socket_listen_sync(QIOChannelSocket *ioc,
+                                   SocketAddress *addr,
+                                   Error **errp);
+
+/**
+ * qio_channel_socket_listen_async:
+ * @ioc: the socket channel object
+ * @addr: the address to listen to
+ * @callback: the function to invoke on completion
+ * @opaque: user data to pass to @callback
+ * @destroy: the function to free @opaque
+ *
+ * Attempt to listen to the address @addr. This method
+ * will run in the background so the caller will regain
+ * execution control immediately. The function @callback
+ * will be invoked on completion or failure.
+ */
+void qio_channel_socket_listen_async(QIOChannelSocket *ioc,
+                                     SocketAddress *addr,
+                                     QIOTaskFunc callback,
+                                     gpointer opaque,
+                                     GDestroyNotify destroy);
+
+
+/**
+ * qio_channel_socket_dgram_sync:
+ * @ioc: the socket channel object
+ * @localAddr: the address to local bind address
+ * @remoteAddr: the address to remote peer address
+ * @errp: pointer to an uninitialized error object
+ *
+ * Attempt to initialize a datagram socket bound to
+ * @localAddr and communicating with peer @remoteAddr.
+ * This method will run in the foreground so the caller
+ * will not regain execution control until the socket
+ * is established or an error occurs.
+ */
+int qio_channel_socket_dgram_sync(QIOChannelSocket *ioc,
+                                  SocketAddress *localAddr,
+                                  SocketAddress *remoteAddr,
+                                  Error **errp);
+
+/**
+ * qio_channel_socket_dgram_async:
+ * @ioc: the socket channel object
+ * @localAddr: the address to local bind address
+ * @remoteAddr: the address to remote peer address
+ * @callback: the function to invoke on completion
+ * @opaque: user data to pass to @callback
+ * @destroy: the function to free @opaque
+ *
+ * Attempt to initialize a datagram socket bound to
+ * @localAddr and communicating with peer @remoteAddr.
+ * This method will run in the background so the caller
+ * will regain execution control immediately. The function
+ * @callback will be invoked on completion or failure.
+ */
+void qio_channel_socket_dgram_async(QIOChannelSocket *ioc,
+                                    SocketAddress *localAddr,
+                                    SocketAddress *remoteAddr,
+                                    QIOTaskFunc callback,
+                                    gpointer opaque,
+                                    GDestroyNotify destroy);
+
+
+/**
+ * qio_channel_socket_get_local_address:
+ * @ioc: the socket channel object
+ * @errp: pointer to an uninitialized error object
+ *
+ * Get the string representation of the local socket
+ * address. A pointer to the allocated address information
+ * struct will be returned, which the caller is required to
+ * release with a call qapi_free_SocketAddress when no
+ * longer required.
+ *
+ * Returns: 0 on success, -1 on error
+ */
+SocketAddress *
+qio_channel_socket_get_local_address(QIOChannelSocket *ioc,
+                                     Error **errp);
+
+/**
+ * qio_channel_socket_get_remote_address:
+ * @ioc: the socket channel object
+ * @errp: pointer to an uninitialized error object
+ *
+ * Get the string representation of the local socket
+ * address. A pointer to the allocated address information
+ * struct will be returned, which the caller is required to
+ * release with a call qapi_free_SocketAddress when no
+ * longer required.
+ *
+ * Returns: the socket address struct, or NULL on error
+ */
+SocketAddress *
+qio_channel_socket_get_remote_address(QIOChannelSocket *ioc,
+                                      Error **errp);
+
+/**
+ * qio_channel_socket_set_nodelay:
+ * @ioc: the socket channel object
+ * @enabled: the new flag state
+ *
+ * Set the state of the TCP_NODELAY socket flag. If the
+ * @enabled parameter is true, then TCP_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_set_cork:
+ * @ioc: the socket channel object
+ * @enabled: the new flag state
+ *
+ * Set the state of the TCP_CORK socket flag. If the
+ * @enabled parameter is true, then TCP_CORK will be
+ * set and data will be not be transmitted immediately.
+ * If @enabled is false, then any previously queued
+ * data is released for transmission. This method is
+ * useful when the TCP_NODELAY flag is enabled to have
+ * direct control over transmission times.
+ */
+void
+qio_channel_socket_set_cork(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 bef2ca1..f12ec26 100644
--- a/io/Makefile.objs
+++ b/io/Makefile.objs
@@ -2,3 +2,4 @@ io-obj-y = buffer.o
 io-obj-y += task.o
 io-obj-y += channel.o
 io-obj-y += channel-watch.o
+io-obj-y += channel-socket.o
diff --git a/io/channel-socket.c b/io/channel-socket.c
new file mode 100644
index 0000000..0b28a00
--- /dev/null
+++ b/io/channel-socket.c
@@ -0,0 +1,718 @@
+/*
+ * 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-watch.h"
+
+#define SOCKET_MAX_FDS 16
+
+SocketAddress *
+qio_channel_socket_get_local_address(QIOChannelSocket *ioc,
+                                     Error **errp)
+{
+    return socket_local_address(ioc->fd, errp);
+}
+
+SocketAddress *
+qio_channel_socket_get_remote_address(QIOChannelSocket *ioc,
+                                      Error **errp)
+{
+    return socket_remote_address(ioc->fd, errp);
+}
+
+QIOChannelSocket *
+qio_channel_socket_new(void)
+{
+    QIOChannelSocket *ioc;
+
+    ioc = QIO_CHANNEL_SOCKET(object_new(TYPE_QIO_CHANNEL_SOCKET));
+    ioc->fd = -1;
+
+    return ioc;
+}
+
+
+static int
+qio_channel_socket_set_fd(QIOChannelSocket *ioc,
+                          int fd,
+                          Error **errp)
+{
+    if (ioc->fd != -1) {
+        error_setg(errp, _("Socket is already open"));
+        return -1;
+    }
+
+    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 0;
+
+ error:
+    ioc->fd = -1; /* Let the caller close FD on failure */
+    return -1;
+}
+
+QIOChannelSocket *
+qio_channel_socket_new_fd(int fd,
+                          Error **errp)
+{
+    QIOChannelSocket *ioc;
+
+    ioc = qio_channel_socket_new();
+    if (qio_channel_socket_set_fd(ioc, fd, errp) < 0) {
+        object_unref(OBJECT(ioc));
+        return NULL;
+    }
+
+    return ioc;
+}
+
+
+int qio_channel_socket_connect_sync(QIOChannelSocket *ioc,
+                                    SocketAddress *addr,
+                                    Error **errp)
+{
+    int fd;
+
+    fd = socket_connect(addr, errp, NULL, NULL);
+    if (fd < 0) {
+        return -1;
+    }
+
+    if (qio_channel_socket_set_fd(ioc, fd, errp) < 0) {
+        close(fd);
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static int qio_channel_socket_connect_worker(QIOTask *task,
+                                             Error **errp,
+                                             gpointer opaque)
+{
+    QIOChannelSocket *ioc = QIO_CHANNEL_SOCKET(qio_task_get_source(task));
+    SocketAddress *addr = opaque;
+
+    return qio_channel_socket_connect_sync(ioc,
+                                           addr,
+                                           errp);
+}
+
+
+void qio_channel_socket_connect_async(QIOChannelSocket *ioc,
+                                      SocketAddress *addr,
+                                      QIOTaskFunc callback,
+                                      gpointer opaque,
+                                      GDestroyNotify destroy)
+{
+    QIOTask *task = qio_task_new(
+        OBJECT(ioc), callback, opaque, destroy);
+    SocketAddress *addrCopy;
+
+    qapi_copy_SocketAddress(&addrCopy, addr);
+
+    /* socket_connect() does a non-blocking connect(), but it
+     * still blocks in DNS lookups, so we must use a thread */
+    qio_task_run_in_thread(task,
+                           qio_channel_socket_connect_worker,
+                           addrCopy,
+                           (GDestroyNotify)qapi_free_SocketAddress);
+}
+
+
+int qio_channel_socket_listen_sync(QIOChannelSocket *ioc,
+                                   SocketAddress *addr,
+                                   Error **errp)
+{
+    int fd;
+
+    fd = socket_listen(addr, errp);
+    if (fd < 0) {
+        return -1;
+    }
+
+    if (qio_channel_socket_set_fd(ioc, fd, errp) < 0) {
+        close(fd);
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static int qio_channel_socket_listen_worker(QIOTask *task,
+                                            Error **errp,
+                                            gpointer opaque)
+{
+    QIOChannelSocket *ioc = QIO_CHANNEL_SOCKET(qio_task_get_source(task));
+    SocketAddress *addr = opaque;
+
+    return qio_channel_socket_listen_sync(ioc,
+                                          addr,
+                                          errp);
+}
+
+
+void qio_channel_socket_listen_async(QIOChannelSocket *ioc,
+                                     SocketAddress *addr,
+                                     QIOTaskFunc callback,
+                                     gpointer opaque,
+                                     GDestroyNotify destroy)
+{
+    QIOTask *task = qio_task_new(
+        OBJECT(ioc), callback, opaque, destroy);
+    SocketAddress *addrCopy;
+
+    qapi_copy_SocketAddress(&addrCopy, addr);
+
+    /* socket_listen() blocks in DNS lookups, so we must use a thread */
+    qio_task_run_in_thread(task,
+                           qio_channel_socket_listen_worker,
+                           addrCopy,
+                           (GDestroyNotify)qapi_free_SocketAddress);
+}
+
+
+int qio_channel_socket_dgram_sync(QIOChannelSocket *ioc,
+                                  SocketAddress *localAddr,
+                                  SocketAddress *remoteAddr,
+                                  Error **errp)
+{
+    int fd;
+
+    fd = socket_dgram(localAddr, remoteAddr, errp);
+    if (fd < 0) {
+        return -1;
+    }
+
+    if (qio_channel_socket_set_fd(ioc, fd, errp) < 0) {
+        close(fd);
+        return -1;
+    }
+
+    return 0;
+}
+
+
+struct QIOChannelSocketDGramWorkerData {
+    SocketAddress *localAddr;
+    SocketAddress *remoteAddr;
+};
+
+
+static void qio_channel_socket_dgram_worker_free(gpointer opaque)
+{
+    struct QIOChannelSocketDGramWorkerData *data = opaque;
+    qapi_free_SocketAddress(data->localAddr);
+    qapi_free_SocketAddress(data->remoteAddr);
+    g_free(data);
+}
+
+static int qio_channel_socket_dgram_worker(QIOTask *task,
+                                           Error **errp,
+                                           gpointer opaque)
+{
+    QIOChannelSocket *ioc = QIO_CHANNEL_SOCKET(qio_task_get_source(task));
+    struct QIOChannelSocketDGramWorkerData *data = opaque;
+
+    /* socket_dgram() blocks in DNS lookups, so we must use a thread */
+    return qio_channel_socket_dgram_sync(ioc,
+                                         data->localAddr,
+                                         data->remoteAddr,
+                                         errp);
+}
+
+
+void qio_channel_socket_dgram_async(QIOChannelSocket *ioc,
+                                    SocketAddress *localAddr,
+                                    SocketAddress *remoteAddr,
+                                    QIOTaskFunc callback,
+                                    gpointer opaque,
+                                    GDestroyNotify destroy)
+{
+    QIOTask *task = qio_task_new(
+        OBJECT(ioc), callback, opaque, destroy);
+    struct QIOChannelSocketDGramWorkerData *data = g_new0(
+        struct QIOChannelSocketDGramWorkerData, 1);
+
+    qapi_copy_SocketAddress(&data->localAddr, localAddr);
+    qapi_copy_SocketAddress(&data->remoteAddr, remoteAddr);
+
+    qio_task_run_in_thread(task,
+                           qio_channel_socket_dgram_worker,
+                           data,
+                           qio_channel_socket_dgram_worker_free);
+}
+
+
+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)
+{
+#ifndef WIN32
+    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
+
+    switch (feature) {
+    case QIO_CHANNEL_FEATURE_FD_PASS:
+        if (sioc->localAddr.ss_family == AF_UNIX) {
+            return true;
+        } else {
+            return false;
+        }
+    default:
+        return false;
+    }
+#else
+    return false;
+#endif
+}
+
+
+#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,
+                                        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;
+
+#ifdef MSG_CMSG_CLOEXEC
+    sflags |= MSG_CMSG_CLOEXEC;
+#endif
+
+    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 ||
+            socket_error() == EWOULDBLOCK) {
+            return QIO_CHANNEL_ERR_BLOCK;
+        }
+        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,
+                                         Error **errp)
+{
+    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
+    ssize_t ret;
+    struct msghdr msg = { NULL, };
+
+    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 ||
+            socket_error() == EWOULDBLOCK) {
+            return QIO_CHANNEL_ERR_BLOCK;
+        }
+        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,
+                                        Error **errp)
+{
+    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
+    ssize_t done = 0;
+    ssize_t i;
+
+    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,
+                                         Error **errp)
+{
+    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
+    ssize_t done = 0;
+    ssize_t i;
+
+    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));
+}
+
+
+void
+qio_channel_socket_set_cork(QIOChannelSocket *ioc,
+                            bool enabled)
+{
+    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
+    int v = enabled ? 1 : 0;
+
+    socket_set_cork(sioc->fd, 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_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);
diff --git a/scripts/create_config b/scripts/create_config
index 546f889..9cb176f 100755
--- a/scripts/create_config
+++ b/scripts/create_config
@@ -61,6 +61,15 @@ case $line in
     value=${line#*=}
     echo "#define $name $value"
     ;;
+ HAVE_*=y) # configuration
+    name=${line%=*}
+    echo "#define $name 1"
+    ;;
+ HAVE_*=*) # configuration
+    name=${line%=*}
+    value=${line#*=}
+    echo "#define $name $value"
+    ;;
  ARCH=*) # configuration
     arch=${line#*=}
     arch_name=`echo $arch | LC_ALL=C tr '[a-z]' '[A-Z]'`
diff --git a/tests/.gitignore b/tests/.gitignore
index 6269480..ac891c6 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -23,6 +23,7 @@ test-cutils
 test-hbitmap
 test-int128
 test-iov
+test-io-channel-socket
 test-io-task
 test-mul64
 test-opts-visitor
diff --git a/tests/Makefile b/tests/Makefile
index 3b7f5a6..37bde1a 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -79,6 +79,7 @@ check-unit-y += tests/test-crypto-cipher$(EXESUF)
 check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlscredsx509$(EXESUF)
 check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlssession$(EXESUF)
 check-unit-y += tests/test-io-task$(EXESUF)
+check-unit-y += tests/test-io-channel-socket$(EXESUF)
 
 check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
 
@@ -360,6 +361,8 @@ tests/test-crypto-tlscredsx509$(EXESUF): tests/test-crypto-tlscredsx509.o \
 tests/test-crypto-tlssession$(EXESUF): tests/test-crypto-tlssession.o \
 	tests/crypto-tls-x509-helpers.o tests/pkix_asn1_tab.o $(test-crypto-obj-y)
 tests/test-io-task$(EXESUF): tests/test-io-task.o $(test-io-obj-y)
+tests/test-io-channel-socket$(EXESUF): tests/test-io-channel-socket.o \
+        tests/io-channel-helpers.o $(test-io-obj-y)
 
 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/io-channel-helpers.c b/tests/io-channel-helpers.c
new file mode 100644
index 0000000..946c9b9
--- /dev/null
+++ b/tests/io-channel-helpers.c
@@ -0,0 +1,222 @@
+/*
+ * QEMU I/O channel test helpers
+ *
+ * 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-helpers.h"
+
+struct TestIOData {
+    QIOChannel *src;
+    QIOChannel *dst;
+    bool blocking;
+    size_t len;
+    size_t niov;
+    char *input;
+    struct iovec *inputv;
+    char *output;
+    struct iovec *outputv;
+    Error *writeerr;
+    Error *readerr;
+};
+
+
+static void test_skip_iovec(struct iovec **iov,
+                            size_t *niov,
+                            size_t skip,
+                            struct iovec *old)
+{
+    size_t offset = 0;
+    size_t i;
+
+    for (i = 0; i < *niov; i++) {
+        if (skip < (*iov)[i].iov_len) {
+            old->iov_len = (*iov)[i].iov_len;
+            old->iov_base = (*iov)[i].iov_base;
+
+            (*iov)[i].iov_len -= skip;
+            (*iov)[i].iov_base += skip;
+            break;
+        } else {
+            skip -= (*iov)[i].iov_len;
+
+            if (i == 0 && old->iov_base) {
+                (*iov)[i].iov_len = old->iov_len;
+                (*iov)[i].iov_base = old->iov_base;
+                old->iov_len = 0;
+                old->iov_base = NULL;
+            }
+
+            offset++;
+        }
+    }
+
+    *iov = *iov + offset;
+    *niov -= offset;
+}
+
+
+/* This thread sends all data using iovecs */
+static gpointer test_io_thread_writer(gpointer opaque)
+{
+    struct TestIOData *data = opaque;
+    struct iovec *iov = data->inputv;
+    size_t niov = data->niov;
+    struct iovec old = { 0 };
+
+    qio_channel_set_blocking(data->src, data->blocking);
+
+    while (niov) {
+        ssize_t ret;
+        ret = qio_channel_writev(data->src,
+                                 iov,
+                                 niov,
+                                 &data->writeerr);
+        if (ret == QIO_CHANNEL_ERR_BLOCK) {
+            if (data->blocking) {
+                error_setg(&data->writeerr,
+                           "Unexpected I/O blocking");
+                break;
+            } else {
+                qio_channel_wait(data->src,
+                                 G_IO_OUT);
+                continue;
+            }
+        } else if (ret < 0) {
+            break;
+        } else if (ret == 0) {
+            error_setg(&data->writeerr,
+                       "Unexpected zero length write");
+            break;
+        }
+
+        test_skip_iovec(&iov, &niov, ret, &old);
+    }
+
+    return NULL;
+}
+
+
+/* This thread receives all data using iovecs */
+static gpointer test_io_thread_reader(gpointer opaque)
+{
+    struct TestIOData *data = opaque;
+    struct iovec *iov = data->outputv;
+    size_t niov = data->niov;
+    struct iovec old = { 0 };
+
+    qio_channel_set_blocking(data->dst, data->blocking);
+
+    while (niov) {
+        ssize_t ret;
+
+        ret = qio_channel_readv(data->dst,
+                                iov,
+                                niov,
+                                &data->readerr);
+
+        if (ret == QIO_CHANNEL_ERR_BLOCK) {
+            if (data->blocking) {
+                error_setg(&data->writeerr,
+                           "Unexpected I/O blocking");
+                break;
+            } else {
+                qio_channel_wait(data->dst,
+                                 G_IO_IN);
+                continue;
+            }
+        } else if (ret < 0) {
+            break;
+        } else if (ret == 0) {
+            break;
+        }
+
+        test_skip_iovec(&iov, &niov, ret, &old);
+    }
+
+    return NULL;
+}
+
+
+static struct TestIOData *test_load_io_data(const char *filename)
+{
+    struct TestIOData *data = g_new0(struct TestIOData, 1);
+    GError *err = NULL;
+    size_t offset;
+    size_t i;
+
+#define IOVEC_LEN (64 * 1024)
+
+    if (!g_file_get_contents(filename, &data->input, &data->len, &err)) {
+        g_printerr("Unable to load file: %s\n", err->message);
+        abort();
+    }
+    data->len++; /* So that we send the trailing \0 too */
+    data->output = g_new0(gchar, data->len);
+
+    data->niov = (data->len / IOVEC_LEN) + 1;
+    data->inputv = g_new0(struct iovec, data->niov);
+    data->outputv = g_new0(struct iovec, data->niov);
+
+    for (i = 0, offset = 0; i < data->niov; i++, offset += IOVEC_LEN) {
+        data->inputv[i].iov_base = data->input + offset;
+        data->outputv[i].iov_base = data->output + offset;
+        if ((data->len - offset) > IOVEC_LEN) {
+            data->inputv[i].iov_len = IOVEC_LEN;
+            data->outputv[i].iov_len = IOVEC_LEN;
+        } else {
+            data->inputv[i].iov_len = data->len - offset;
+            data->outputv[i].iov_len = data->len - offset;
+        }
+    }
+
+    return data;
+}
+
+
+void test_io_channel_comms(bool blocking,
+                           QIOChannel *src,
+                           QIOChannel *dst)
+{
+    struct TestIOData *data;
+    GThread *reader, *writer;
+
+    data = test_load_io_data("libqemuutil.a");
+    data->src = src;
+    data->dst = dst;
+    data->blocking = blocking;
+
+    reader = g_thread_new("reader",
+                          test_io_thread_reader,
+                          data);
+    writer = g_thread_new("writer",
+                          test_io_thread_writer,
+                          data);
+
+    g_thread_join(reader);
+    g_thread_join(writer);
+
+    g_assert_cmpint(memcmp(data->input,
+                           data->output,
+                           data->len), ==, 0);
+    g_assert(data->readerr == NULL);
+    g_assert(data->writeerr == NULL);
+
+    g_free(data->input);
+    g_free(data->output);
+    g_free(data);
+}
diff --git a/tests/io-channel-helpers.h b/tests/io-channel-helpers.h
new file mode 100644
index 0000000..d0085a2
--- /dev/null
+++ b/tests/io-channel-helpers.h
@@ -0,0 +1,30 @@
+/*
+ * QEMU I/O channel test helpers
+ *
+ * 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"
+
+#ifndef TEST_IO_CHANNEL_HELPERS
+#define TEST_IO_CHANNEL_HELPERS
+
+void test_io_channel_comms(bool blocking,
+                           QIOChannel *src,
+                           QIOChannel *dst);
+
+#endif /* TEST_IO_CHANNEL_HELPERS */
diff --git a/tests/test-io-channel-socket.c b/tests/test-io-channel-socket.c
new file mode 100644
index 0000000..338f816
--- /dev/null
+++ b/tests/test-io-channel-socket.c
@@ -0,0 +1,349 @@
+/*
+ * QEMU I/O channel sockets test
+ *
+ * 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-socket.h"
+#include "io-channel-helpers.h"
+#ifdef HAVE_IFADDRS_H
+#include <ifaddrs.h>
+#endif
+
+static int check_protocol_support(bool *has_ipv4, bool *has_ipv6)
+{
+#ifdef HAVE_IFADDRS_H
+    struct ifaddrs *ifaddr = NULL, *ifa;
+    struct addrinfo hints = { 0 };
+    struct addrinfo *ai = NULL;
+    int gaierr;
+
+    *has_ipv4 = *has_ipv6 = false;
+
+    if (getifaddrs(&ifaddr) < 0) {
+        g_printerr("Failed to lookup interface addresses: %s\n",
+                   strerror(errno));
+        return -1;
+    }
+
+    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
+        if (!ifa->ifa_addr)
+            continue;
+
+        if (ifa->ifa_addr->sa_family == AF_INET)
+            *has_ipv4 = true;
+        if (ifa->ifa_addr->sa_family == AF_INET6)
+            *has_ipv6 = true;
+    }
+
+    freeifaddrs(ifaddr);
+
+    hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
+    hints.ai_family = AF_INET6;
+    hints.ai_socktype = SOCK_STREAM;
+
+    if ((gaierr = getaddrinfo("::1", NULL, &hints, &ai)) != 0) {
+        if (gaierr == EAI_ADDRFAMILY ||
+            gaierr == EAI_FAMILY ||
+            gaierr == EAI_NONAME) {
+            *has_ipv6 = false;
+        } else {
+            g_printerr("Failed to resolve ::1 address: %s\n",
+                       gai_strerror(gaierr));
+            return -1;
+        }
+    }
+
+    freeaddrinfo(ai);
+
+    return 0;
+#else
+    *has_ipv4 = *has_ipv6 = false;
+
+    return -1;
+#endif
+}
+
+
+static void test_io_channel_set_socket_bufs(QIOChannel *src,
+                                            QIOChannel *dst)
+{
+    int buflen = 64 * 1024;
+
+    /*
+     * Make the socket buffers small so that we see
+     * the effects of partial reads/writes
+     */
+    setsockopt(((QIOChannelSocket *)src)->fd,
+               SOL_SOCKET, SO_SNDBUF,
+               (char *)&buflen,
+               sizeof(buflen));
+
+    setsockopt(((QIOChannelSocket *)dst)->fd,
+               SOL_SOCKET, SO_SNDBUF,
+               (char *)&buflen,
+               sizeof(buflen));
+}
+
+
+static void test_io_channel_setup_sync(SocketAddress *listen_addr,
+                                       SocketAddress *connect_addr,
+                                       QIOChannel **src,
+                                       QIOChannel **dst)
+{
+    QIOChannelSocket *lioc;
+
+    lioc = qio_channel_socket_new();
+    qio_channel_socket_listen_sync(lioc, listen_addr, &error_abort);
+
+    if (listen_addr->kind == SOCKET_ADDRESS_KIND_INET) {
+        SocketAddress *laddr = qio_channel_socket_get_local_address(
+            lioc, &error_abort);
+
+        connect_addr->inet->port = g_strdup(laddr->inet->port);
+
+        qapi_free_SocketAddress(laddr);
+    }
+
+    *src = QIO_CHANNEL(qio_channel_socket_new());
+    qio_channel_socket_connect_sync(
+        QIO_CHANNEL_SOCKET(*src), connect_addr, &error_abort);
+    qio_channel_socket_set_nodelay(QIO_CHANNEL_SOCKET(*src), true);
+
+    *dst = QIO_CHANNEL(qio_channel_socket_accept(lioc, &error_abort));
+    g_assert(*dst);
+
+    test_io_channel_set_socket_bufs(*src, *dst);
+
+    object_unref(OBJECT(lioc));
+}
+
+
+struct TestIOChannelData {
+    bool err;
+    GMainLoop *loop;
+};
+
+
+static void test_io_channel_complete(Object *src,
+                                     Error *err,
+                                     gpointer opaque)
+{
+    struct TestIOChannelData *data = opaque;
+    data->err = err != NULL;
+    g_main_loop_quit(data->loop);
+}
+
+
+static void test_io_channel_setup_async(SocketAddress *listen_addr,
+                                        SocketAddress *connect_addr,
+                                        QIOChannel **src,
+                                        QIOChannel **dst)
+{
+    QIOChannelSocket *lioc;
+    struct TestIOChannelData data;
+
+    data.loop = g_main_loop_new(g_main_context_default(),
+                                TRUE);
+
+    lioc = qio_channel_socket_new();
+    qio_channel_socket_listen_async(
+        lioc, listen_addr,
+        test_io_channel_complete, &data, NULL);
+
+    g_main_loop_run(data.loop);
+
+    g_assert(!data.err);
+
+    if (listen_addr->kind == SOCKET_ADDRESS_KIND_INET) {
+        SocketAddress *laddr = qio_channel_socket_get_local_address(
+            lioc, &error_abort);
+
+        connect_addr->inet->port = g_strdup(laddr->inet->port);
+
+        qapi_free_SocketAddress(laddr);
+    }
+
+    *src = QIO_CHANNEL(qio_channel_socket_new());
+    qio_channel_socket_connect_async(
+        QIO_CHANNEL_SOCKET(*src), connect_addr,
+        test_io_channel_complete, &data, NULL);
+
+    g_main_loop_run(data.loop);
+
+    g_assert(!data.err);
+
+    *dst = QIO_CHANNEL(qio_channel_socket_accept(lioc, &error_abort));
+    g_assert(*dst);
+
+    qio_channel_socket_set_nodelay(QIO_CHANNEL_SOCKET(*src), true);
+    test_io_channel_set_socket_bufs(*src, *dst);
+
+    object_unref(OBJECT(lioc));
+}
+
+
+static void test_io_channel(bool async,
+                            SocketAddress *listen_addr,
+                            SocketAddress *connect_addr)
+{
+    QIOChannel *src, *dst;
+    if (async) {
+        test_io_channel_setup_async(listen_addr, connect_addr, &src, &dst);
+        test_io_channel_comms(true, src, dst);
+        object_unref(OBJECT(src));
+        object_unref(OBJECT(dst));
+
+        test_io_channel_setup_async(listen_addr, connect_addr, &src, &dst);
+        test_io_channel_comms(false, src, dst);
+        object_unref(OBJECT(src));
+        object_unref(OBJECT(dst));
+    } else {
+        test_io_channel_setup_sync(listen_addr, connect_addr, &src, &dst);
+        test_io_channel_comms(true, src, dst);
+        object_unref(OBJECT(src));
+        object_unref(OBJECT(dst));
+
+        test_io_channel_setup_sync(listen_addr, connect_addr, &src, &dst);
+        test_io_channel_comms(false, src, dst);
+        object_unref(OBJECT(src));
+        object_unref(OBJECT(dst));
+    }
+}
+
+
+static void test_io_channel_ipv4(bool async)
+{
+    SocketAddress *listen_addr = g_new0(SocketAddress, 1);
+    SocketAddress *connect_addr = g_new0(SocketAddress, 1);
+
+    listen_addr->kind = SOCKET_ADDRESS_KIND_INET;
+    listen_addr->inet = g_new0(InetSocketAddress, 1);
+    listen_addr->inet->host = g_strdup("0.0.0.0");
+    listen_addr->inet->port = NULL; /* Auto-select */
+
+    connect_addr->kind = SOCKET_ADDRESS_KIND_INET;
+    connect_addr->inet = g_new0(InetSocketAddress, 1);
+    connect_addr->inet->host = g_strdup("127.0.0.1");
+    connect_addr->inet->port = NULL; /* Filled in later */
+
+    test_io_channel(async, listen_addr, connect_addr);
+}
+
+
+static void test_io_channel_ipv4_sync(void)
+{
+    return test_io_channel_ipv4(false);
+}
+
+
+static void test_io_channel_ipv4_async(void)
+{
+    return test_io_channel_ipv4(true);
+}
+
+
+static void test_io_channel_ipv6(bool async)
+{
+    SocketAddress *listen_addr = g_new0(SocketAddress, 1);
+    SocketAddress *connect_addr = g_new0(SocketAddress, 1);
+
+    listen_addr->kind = SOCKET_ADDRESS_KIND_INET;
+    listen_addr->inet = g_new0(InetSocketAddress, 1);
+    listen_addr->inet->host = g_strdup("::");
+    listen_addr->inet->port = NULL; /* Auto-select */
+
+    connect_addr->kind = SOCKET_ADDRESS_KIND_INET;
+    connect_addr->inet = g_new0(InetSocketAddress, 1);
+    connect_addr->inet->host = g_strdup("::1");
+    connect_addr->inet->port = NULL; /* Filled in later */
+
+    test_io_channel(async, listen_addr, connect_addr);
+}
+
+
+static void test_io_channel_ipv6_sync(void)
+{
+    return test_io_channel_ipv6(false);
+}
+
+
+static void test_io_channel_ipv6_async(void)
+{
+    return test_io_channel_ipv6(true);
+}
+
+
+#ifndef _WIN32
+static void test_io_channel_unix(bool async)
+{
+    SocketAddress *listen_addr = g_new0(SocketAddress, 1);
+    SocketAddress *connect_addr = g_new0(SocketAddress, 1);
+
+    listen_addr->kind = SOCKET_ADDRESS_KIND_UNIX;
+    listen_addr->q_unix = g_new0(UnixSocketAddress, 1);
+    listen_addr->q_unix->path = g_strdup("test-io-channel-socket.sock");
+
+    connect_addr->kind = SOCKET_ADDRESS_KIND_UNIX;
+    connect_addr->q_unix = g_new0(UnixSocketAddress, 1);
+    connect_addr->q_unix->path = g_strdup("test-io-channel-socket.sock");
+
+    test_io_channel(async, listen_addr, connect_addr);
+}
+
+
+static void test_io_channel_unix_sync(void)
+{
+    return test_io_channel_unix(false);
+}
+
+
+static void test_io_channel_unix_async(void)
+{
+    return test_io_channel_unix(true);
+}
+#endif /* _WIN32 */
+
+
+int main(int argc, char **argv)
+{
+    bool has_ipv4, has_ipv6;
+
+    module_call_init(MODULE_INIT_QOM);
+
+    g_test_init(&argc, &argv, NULL);
+
+    if (check_protocol_support(&has_ipv4, &has_ipv6) < 0) {
+        return 1;
+    }
+
+    if (has_ipv4) {
+        g_test_add_func("/io/channel/socket/ipv4-sync", test_io_channel_ipv4_sync);
+        g_test_add_func("/io/channel/socket/ipv4-async", test_io_channel_ipv4_async);
+    }
+    if (has_ipv6) {
+        g_test_add_func("/io/channel/socket/ipv6-sync", test_io_channel_ipv6_sync);
+        g_test_add_func("/io/channel/socket/ipv6-async", test_io_channel_ipv6_async);
+    }
+
+#ifndef _WIN32
+    g_test_add_func("/io/channel/socket/unix-sync", test_io_channel_unix_sync);
+    g_test_add_func("/io/channel/socket/unix-async", test_io_channel_unix_async);
+#endif /* _WIN32 */
+
+    return g_test_run();
+}
-- 
2.4.3

  parent reply	other threads:[~2015-09-03 15:40 UTC|newest]

Thread overview: 57+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-09-03 15:38 [Qemu-devel] [PATCH FYI 00/46] Generic TLS support across VNC/chardev/migration Daniel P. Berrange
2015-09-03 15:38 ` [Qemu-devel] [PATCH FYI 01/46] sockets: add helpers for creating SocketAddress from a socket Daniel P. Berrange
2015-09-03 15:38 ` [Qemu-devel] [PATCH FYI 02/46] sockets: move qapi_copy_SocketAddress into qemu-sockets.c Daniel P. Berrange
2015-09-03 15:38 ` [Qemu-devel] [PATCH FYI 03/46] sockets: allow port to be NULL when listening on IP address Daniel P. Berrange
2015-09-03 15:38 ` [Qemu-devel] [PATCH FYI 04/46] osdep: add qemu_fork() wrapper for safely handling signals Daniel P. Berrange
2015-09-03 15:38 ` [Qemu-devel] [PATCH FYI 05/46] coroutine: move into libqemuutil.a library Daniel P. Berrange
2015-09-03 15:38 ` [Qemu-devel] [PATCH FYI 06/46] io: add abstract QIOChannel classes Daniel P. Berrange
2015-09-03 15:38 ` [Qemu-devel] [PATCH FYI 07/46] io: add helper module for creating watches on FDs Daniel P. Berrange
2015-09-03 15:38 ` [Qemu-devel] [PATCH FYI 08/46] io: pull Buffer code out of VNC module Daniel P. Berrange
2015-09-03 15:38 ` [Qemu-devel] [PATCH FYI 09/46] io: add QIOTask class for async operations Daniel P. Berrange
2015-09-03 15:38 ` Daniel P. Berrange [this message]
2015-09-03 15:38 ` [Qemu-devel] [PATCH FYI 11/46] io: add QIOChannelFile class Daniel P. Berrange
2015-09-03 15:38 ` [Qemu-devel] [PATCH FYI 12/46] io: add QIOChannelTLS class Daniel P. Berrange
2015-09-07 15:31   ` Dr. David Alan Gilbert
2015-09-07 15:41     ` Daniel P. Berrange
2015-09-07 15:51       ` Dr. David Alan Gilbert
2015-09-07 16:04         ` Daniel P. Berrange
2015-09-03 15:38 ` [Qemu-devel] [PATCH FYI 13/46] io: add QIOChannelWebsock class Daniel P. Berrange
2015-09-07 15:44   ` Dr. David Alan Gilbert
2015-09-07 15:50     ` Daniel P. Berrange
2015-09-03 15:38 ` [Qemu-devel] [PATCH FYI 14/46] io: add QIOChannelCommand class Daniel P. Berrange
2015-09-03 15:38 ` [Qemu-devel] [PATCH FYI 15/46] ui: convert VNC startup code to use SocketAddress Daniel P. Berrange
2015-09-03 15:38 ` [Qemu-devel] [PATCH FYI 16/46] ui: convert VNC server to use QIOChannelSocket Daniel P. Berrange
2015-09-03 15:38 ` [Qemu-devel] [PATCH FYI 17/46] ui: convert VNC server to use QIOChannelTLS Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 18/46] ui: convert VNC server to use QIOChannelWebsock Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 19/46] char: remove fixed length filename allocation Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 20/46] char: convert from GIOChannel to QIOChannel Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 21/46] char: don't assume telnet initialization will not block Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 22/46] char: introduce support for TLS encrypted TCP chardev backend Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 23/46] nbd: convert to use the QAPI SocketAddress object Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 24/46] qemu-nbd: " Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 25/46] sockets: remove use of QemuOpts from header file Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 26/46] sockets: remove use of QemuOpts from socket_listen Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 27/46] sockets: remove use of QemuOpts from socket_connect Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 28/46] sockets: remove use of QemuOpts from socket_dgram Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 29/46] migration: remove use of qemu_bufopen from vmstate tests Daniel P. Berrange
2015-09-07 16:08   ` Dr. David Alan Gilbert
2015-09-07 16:17     ` Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 30/46] migration: remove memory buffer based QEMUFile backend Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 31/46] migration: move definition of struct QEMUFile back into qemu-file.c Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 32/46] migration: split migration hooks out of QEMUFileOps Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 33/46] migration: ensure qemu_fflush() always writes full data amount Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 34/46] migration: introduce qemu_fset_blocking function on QEMUFile Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 35/46] migration: force QEMUFile to blocking mode for outgoing migration Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 36/46] migration: introduce a new QEMUFile impl based on QIOChannel Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 37/46] migration: convert unix socket protocol to use QIOChannel Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 38/46] migration: convert tcp " Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 39/46] migration: convert fd " Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 40/46] migration: convert exec " Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 41/46] migration: convert RDMA to use QIOChannel interface Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 42/46] migration: convert savevm to use QIOChannel for writing to files Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 43/46] migration: delete QEMUFile sockets implementation Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 44/46] migration: delete QEMUFile stdio implementation Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 45/46] migration: support TLS encryption with TCP migration backend Daniel P. Berrange
2015-09-07 16:23   ` Dr. David Alan Gilbert
2015-09-07 16:29     ` Daniel P. Berrange
2015-09-03 15:39 ` [Qemu-devel] [PATCH FYI 46/46] migration: remove support for non-iovec based write handlers Daniel P. Berrange

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=1441294768-8712-11-git-send-email-berrange@redhat.com \
    --to=berrange@redhat.com \
    --cc=amit.shah@redhat.com \
    --cc=dgilbert@redhat.com \
    --cc=kraxel@redhat.com \
    --cc=pbonzini@redhat.com \
    --cc=qemu-devel@nongnu.org \
    --cc=quintela@redhat.com \
    /path/to/YOUR_REPLY

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

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