io: add QIOChannelSocket class
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>
This commit is contained in:
parent
b02db2d920
commit
559607ea17
11
configure
vendored
11
configure
vendored
@ -2426,6 +2426,14 @@ else
|
|||||||
fi
|
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
|
# VTE probe
|
||||||
|
|
||||||
@ -5137,6 +5145,9 @@ fi
|
|||||||
if test "$tasn1" = "yes" ; then
|
if test "$tasn1" = "yes" ; then
|
||||||
echo "CONFIG_TASN1=y" >> $config_host_mak
|
echo "CONFIG_TASN1=y" >> $config_host_mak
|
||||||
fi
|
fi
|
||||||
|
if test "$have_ifaddrs_h" = "yes" ; then
|
||||||
|
echo "HAVE_IFADDRS_H=y" >> $config_host_mak
|
||||||
|
fi
|
||||||
if test "$vte" = "yes" ; then
|
if test "$vte" = "yes" ; then
|
||||||
echo "CONFIG_VTE=y" >> $config_host_mak
|
echo "CONFIG_VTE=y" >> $config_host_mak
|
||||||
echo "VTE_CFLAGS=$vte_cflags" >> $config_host_mak
|
echo "VTE_CFLAGS=$vte_cflags" >> $config_host_mak
|
||||||
|
244
include/io/channel-socket.h
Normal file
244
include/io/channel-socket.h
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
/*
|
||||||
|
* 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_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);
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* QIO_CHANNEL_SOCKET_H__ */
|
@ -88,6 +88,25 @@ int socket_dgram(SocketAddress *remote, SocketAddress *local, Error **errp);
|
|||||||
int parse_host_port(struct sockaddr_in *saddr, const char *str);
|
int parse_host_port(struct sockaddr_in *saddr, const char *str);
|
||||||
int socket_init(void);
|
int socket_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* socket_sockaddr_to_address:
|
||||||
|
* @sa: socket address struct
|
||||||
|
* @salen: size of @sa struct
|
||||||
|
* @errp: pointer to uninitialized error object
|
||||||
|
*
|
||||||
|
* Get the string representation of the 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 *
|
||||||
|
socket_sockaddr_to_address(struct sockaddr_storage *sa,
|
||||||
|
socklen_t salen,
|
||||||
|
Error **errp);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* socket_local_address:
|
* socket_local_address:
|
||||||
* @fd: the socket file handle
|
* @fd: the socket file handle
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
io-obj-y = channel.o
|
io-obj-y = channel.o
|
||||||
|
io-obj-y += channel-socket.o
|
||||||
io-obj-y += channel-watch.o
|
io-obj-y += channel-watch.o
|
||||||
io-obj-y += task.o
|
io-obj-y += task.o
|
||||||
|
741
io/channel-socket.c
Normal file
741
io/channel-socket.c
Normal file
@ -0,0 +1,741 @@
|
|||||||
|
/*
|
||||||
|
* 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 "io/channel-socket.h"
|
||||||
|
#include "io/channel-watch.h"
|
||||||
|
#include "trace.h"
|
||||||
|
|
||||||
|
#define SOCKET_MAX_FDS 16
|
||||||
|
|
||||||
|
SocketAddress *
|
||||||
|
qio_channel_socket_get_local_address(QIOChannelSocket *ioc,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
return socket_sockaddr_to_address(&ioc->localAddr,
|
||||||
|
ioc->localAddrLen,
|
||||||
|
errp);
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketAddress *
|
||||||
|
qio_channel_socket_get_remote_address(QIOChannelSocket *ioc,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
return socket_sockaddr_to_address(&ioc->remoteAddr,
|
||||||
|
ioc->remoteAddrLen,
|
||||||
|
errp);
|
||||||
|
}
|
||||||
|
|
||||||
|
QIOChannelSocket *
|
||||||
|
qio_channel_socket_new(void)
|
||||||
|
{
|
||||||
|
QIOChannelSocket *sioc;
|
||||||
|
QIOChannel *ioc;
|
||||||
|
|
||||||
|
sioc = QIO_CHANNEL_SOCKET(object_new(TYPE_QIO_CHANNEL_SOCKET));
|
||||||
|
sioc->fd = -1;
|
||||||
|
|
||||||
|
ioc = QIO_CHANNEL(sioc);
|
||||||
|
ioc->features |= (1 << QIO_CHANNEL_FEATURE_SHUTDOWN);
|
||||||
|
|
||||||
|
trace_qio_channel_socket_new(sioc);
|
||||||
|
|
||||||
|
return sioc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int
|
||||||
|
qio_channel_socket_set_fd(QIOChannelSocket *sioc,
|
||||||
|
int fd,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
if (sioc->fd != -1) {
|
||||||
|
error_setg(errp, "Socket is already open");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sioc->fd = fd;
|
||||||
|
sioc->remoteAddrLen = sizeof(sioc->remoteAddr);
|
||||||
|
sioc->localAddrLen = sizeof(sioc->localAddr);
|
||||||
|
|
||||||
|
|
||||||
|
if (getpeername(fd, (struct sockaddr *)&sioc->remoteAddr,
|
||||||
|
&sioc->remoteAddrLen) < 0) {
|
||||||
|
if (socket_error() == ENOTCONN) {
|
||||||
|
memset(&sioc->remoteAddr, 0, sizeof(sioc->remoteAddr));
|
||||||
|
sioc->remoteAddrLen = sizeof(sioc->remoteAddr);
|
||||||
|
} else {
|
||||||
|
error_setg_errno(errp, socket_error(),
|
||||||
|
"Unable to query remote socket address");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getsockname(fd, (struct sockaddr *)&sioc->localAddr,
|
||||||
|
&sioc->localAddrLen) < 0) {
|
||||||
|
error_setg_errno(errp, socket_error(),
|
||||||
|
"Unable to query local socket address");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef WIN32
|
||||||
|
if (sioc->localAddr.ss_family == AF_UNIX) {
|
||||||
|
QIOChannel *ioc = QIO_CHANNEL(sioc);
|
||||||
|
ioc->features |= (1 << QIO_CHANNEL_FEATURE_FD_PASS);
|
||||||
|
}
|
||||||
|
#endif /* WIN32 */
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error:
|
||||||
|
sioc->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;
|
||||||
|
}
|
||||||
|
|
||||||
|
trace_qio_channel_socket_new_fd(ioc, fd);
|
||||||
|
|
||||||
|
return ioc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int qio_channel_socket_connect_sync(QIOChannelSocket *ioc,
|
||||||
|
SocketAddress *addr,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
trace_qio_channel_socket_connect_sync(ioc, addr);
|
||||||
|
fd = socket_connect(addr, errp, NULL, NULL);
|
||||||
|
if (fd < 0) {
|
||||||
|
trace_qio_channel_socket_connect_fail(ioc);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
trace_qio_channel_socket_connect_complete(ioc, fd);
|
||||||
|
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;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = qio_channel_socket_connect_sync(ioc,
|
||||||
|
addr,
|
||||||
|
errp);
|
||||||
|
|
||||||
|
object_unref(OBJECT(ioc));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 */
|
||||||
|
trace_qio_channel_socket_connect_async(ioc, addr);
|
||||||
|
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;
|
||||||
|
|
||||||
|
trace_qio_channel_socket_listen_sync(ioc, addr);
|
||||||
|
fd = socket_listen(addr, errp);
|
||||||
|
if (fd < 0) {
|
||||||
|
trace_qio_channel_socket_listen_fail(ioc);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
trace_qio_channel_socket_listen_complete(ioc, fd);
|
||||||
|
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;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = qio_channel_socket_listen_sync(ioc,
|
||||||
|
addr,
|
||||||
|
errp);
|
||||||
|
|
||||||
|
object_unref(OBJECT(ioc));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 */
|
||||||
|
trace_qio_channel_socket_listen_async(ioc, addr);
|
||||||
|
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;
|
||||||
|
|
||||||
|
trace_qio_channel_socket_dgram_sync(ioc, localAddr, remoteAddr);
|
||||||
|
fd = socket_dgram(localAddr, remoteAddr, errp);
|
||||||
|
if (fd < 0) {
|
||||||
|
trace_qio_channel_socket_dgram_fail(ioc);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
trace_qio_channel_socket_dgram_complete(ioc, fd);
|
||||||
|
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;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* socket_dgram() blocks in DNS lookups, so we must use a thread */
|
||||||
|
ret = qio_channel_socket_dgram_sync(ioc,
|
||||||
|
data->localAddr,
|
||||||
|
data->remoteAddr,
|
||||||
|
errp);
|
||||||
|
|
||||||
|
object_unref(OBJECT(ioc));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
trace_qio_channel_socket_dgram_async(ioc, localAddr, 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:
|
||||||
|
trace_qio_channel_socket_accept(ioc);
|
||||||
|
cioc->fd = accept(ioc->fd, (struct sockaddr *)&cioc->remoteAddr,
|
||||||
|
&cioc->remoteAddrLen);
|
||||||
|
if (cioc->fd < 0) {
|
||||||
|
trace_qio_channel_socket_accept_fail(ioc);
|
||||||
|
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(),
|
||||||
|
"Unable to query local socket address");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
trace_qio_channel_socket_accept_complete(ioc, cioc, cioc->fd);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#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(),
|
||||||
|
"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(),
|
||||||
|
"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;
|
||||||
|
|
||||||
|
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(),
|
||||||
|
"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;
|
||||||
|
|
||||||
|
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(),
|
||||||
|
"Unable to write to socket");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done += ret;
|
||||||
|
if (ret < iov[i].iov_len) {
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
#endif /* WIN32 */
|
||||||
|
|
||||||
|
static int
|
||||||
|
qio_channel_socket_set_blocking(QIOChannel *ioc,
|
||||||
|
bool enabled,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
qemu_set_block(sioc->fd);
|
||||||
|
} else {
|
||||||
|
qemu_set_nonblock(sioc->fd);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
qio_channel_socket_set_delay(QIOChannel *ioc,
|
||||||
|
bool enabled)
|
||||||
|
{
|
||||||
|
QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
|
||||||
|
int v = enabled ? 0 : 1;
|
||||||
|
|
||||||
|
qemu_setsockopt(sioc->fd,
|
||||||
|
IPPROTO_TCP, TCP_NODELAY,
|
||||||
|
&v, sizeof(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
qio_channel_socket_set_cork(QIOChannel *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(),
|
||||||
|
"Unable to close socket");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
sioc->fd = -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
qio_channel_socket_shutdown(QIOChannel *ioc,
|
||||||
|
QIOChannelShutdown how,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(ioc);
|
||||||
|
int sockhow;
|
||||||
|
|
||||||
|
switch (how) {
|
||||||
|
case QIO_CHANNEL_SHUTDOWN_READ:
|
||||||
|
sockhow = SHUT_RD;
|
||||||
|
break;
|
||||||
|
case QIO_CHANNEL_SHUTDOWN_WRITE:
|
||||||
|
sockhow = SHUT_WR;
|
||||||
|
break;
|
||||||
|
case QIO_CHANNEL_SHUTDOWN_BOTH:
|
||||||
|
default:
|
||||||
|
sockhow = SHUT_RDWR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shutdown(sioc->fd, sockhow) < 0) {
|
||||||
|
error_setg_errno(errp, socket_error(),
|
||||||
|
"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_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_shutdown = qio_channel_socket_shutdown;
|
||||||
|
ioc_klass->io_set_cork = qio_channel_socket_set_cork;
|
||||||
|
ioc_klass->io_set_delay = qio_channel_socket_set_delay;
|
||||||
|
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);
|
@ -61,6 +61,15 @@ case $line in
|
|||||||
value=${line#*=}
|
value=${line#*=}
|
||||||
echo "#define $name $value"
|
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=*) # configuration
|
||||||
arch=${line#*=}
|
arch=${line#*=}
|
||||||
arch_name=`echo $arch | LC_ALL=C tr '[a-z]' '[A-Z]'`
|
arch_name=`echo $arch | LC_ALL=C tr '[a-z]' '[A-Z]'`
|
||||||
|
1
tests/.gitignore
vendored
1
tests/.gitignore
vendored
@ -24,6 +24,7 @@ test-cutils
|
|||||||
test-hbitmap
|
test-hbitmap
|
||||||
test-int128
|
test-int128
|
||||||
test-iov
|
test-iov
|
||||||
|
test-io-channel-socket
|
||||||
test-io-task
|
test-io-task
|
||||||
test-mul64
|
test-mul64
|
||||||
test-opts-visitor
|
test-opts-visitor
|
||||||
|
@ -85,6 +85,7 @@ check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlssession$(EXESUF)
|
|||||||
check-unit-$(CONFIG_LINUX) += tests/test-qga$(EXESUF)
|
check-unit-$(CONFIG_LINUX) += tests/test-qga$(EXESUF)
|
||||||
check-unit-y += tests/test-timed-average$(EXESUF)
|
check-unit-y += tests/test-timed-average$(EXESUF)
|
||||||
check-unit-y += tests/test-io-task$(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
|
check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
|
||||||
|
|
||||||
@ -472,6 +473,8 @@ tests/test-crypto-tlssession.o-cflags := $(TASN1_CFLAGS)
|
|||||||
tests/test-crypto-tlssession$(EXESUF): tests/test-crypto-tlssession.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/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-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/pci.o tests/libqos/fw_cfg.o tests/libqos/malloc.o
|
||||||
libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o
|
libqos-obj-y += tests/libqos/i2c.o tests/libqos/libqos.o
|
||||||
|
246
tests/io-channel-helpers.c
Normal file
246
tests/io-channel-helpers.c
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
/*
|
||||||
|
* 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 QIOChannelTest {
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
QIOChannelTest *data = opaque;
|
||||||
|
struct iovec *iov = data->inputv;
|
||||||
|
size_t niov = data->niov;
|
||||||
|
struct iovec old = { 0 };
|
||||||
|
|
||||||
|
qio_channel_set_blocking(data->src, data->blocking, NULL);
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
QIOChannelTest *data = opaque;
|
||||||
|
struct iovec *iov = data->outputv;
|
||||||
|
size_t niov = data->niov;
|
||||||
|
struct iovec old = { 0 };
|
||||||
|
|
||||||
|
qio_channel_set_blocking(data->dst, data->blocking, NULL);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QIOChannelTest *qio_channel_test_new(void)
|
||||||
|
{
|
||||||
|
QIOChannelTest *data = g_new0(QIOChannelTest, 1);
|
||||||
|
size_t i;
|
||||||
|
size_t offset;
|
||||||
|
|
||||||
|
|
||||||
|
/* We'll send 1 MB of data */
|
||||||
|
#define CHUNK_COUNT 250
|
||||||
|
#define CHUNK_LEN 4194
|
||||||
|
|
||||||
|
data->len = CHUNK_COUNT * CHUNK_LEN;
|
||||||
|
data->input = g_new0(char, data->len);
|
||||||
|
data->output = g_new0(gchar, data->len);
|
||||||
|
|
||||||
|
/* Fill input with a pattern */
|
||||||
|
for (i = 0; i < data->len; i += CHUNK_LEN) {
|
||||||
|
memset(data->input + i, (i / CHUNK_LEN), CHUNK_LEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We'll split the data across a bunch of IO vecs */
|
||||||
|
data->niov = CHUNK_COUNT;
|
||||||
|
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 += CHUNK_LEN) {
|
||||||
|
data->inputv[i].iov_base = data->input + offset;
|
||||||
|
data->outputv[i].iov_base = data->output + offset;
|
||||||
|
data->inputv[i].iov_len = CHUNK_LEN;
|
||||||
|
data->outputv[i].iov_len = CHUNK_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void qio_channel_test_run_threads(QIOChannelTest *test,
|
||||||
|
bool blocking,
|
||||||
|
QIOChannel *src,
|
||||||
|
QIOChannel *dst)
|
||||||
|
{
|
||||||
|
GThread *reader, *writer;
|
||||||
|
|
||||||
|
test->src = src;
|
||||||
|
test->dst = dst;
|
||||||
|
test->blocking = blocking;
|
||||||
|
|
||||||
|
reader = g_thread_new("reader",
|
||||||
|
test_io_thread_reader,
|
||||||
|
test);
|
||||||
|
writer = g_thread_new("writer",
|
||||||
|
test_io_thread_writer,
|
||||||
|
test);
|
||||||
|
|
||||||
|
g_thread_join(reader);
|
||||||
|
g_thread_join(writer);
|
||||||
|
|
||||||
|
test->dst = test->src = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void qio_channel_test_run_writer(QIOChannelTest *test,
|
||||||
|
QIOChannel *src)
|
||||||
|
{
|
||||||
|
test->src = src;
|
||||||
|
test_io_thread_writer(test);
|
||||||
|
test->src = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void qio_channel_test_run_reader(QIOChannelTest *test,
|
||||||
|
QIOChannel *dst)
|
||||||
|
{
|
||||||
|
test->dst = dst;
|
||||||
|
test_io_thread_reader(test);
|
||||||
|
test->dst = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void qio_channel_test_validate(QIOChannelTest *test)
|
||||||
|
{
|
||||||
|
g_assert_cmpint(memcmp(test->input,
|
||||||
|
test->output,
|
||||||
|
test->len), ==, 0);
|
||||||
|
g_assert(test->readerr == NULL);
|
||||||
|
g_assert(test->writeerr == NULL);
|
||||||
|
|
||||||
|
g_free(test->inputv);
|
||||||
|
g_free(test->outputv);
|
||||||
|
g_free(test->input);
|
||||||
|
g_free(test->output);
|
||||||
|
g_free(test);
|
||||||
|
}
|
42
tests/io-channel-helpers.h
Normal file
42
tests/io-channel-helpers.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
typedef struct QIOChannelTest QIOChannelTest;
|
||||||
|
|
||||||
|
QIOChannelTest *qio_channel_test_new(void);
|
||||||
|
|
||||||
|
void qio_channel_test_run_threads(QIOChannelTest *test,
|
||||||
|
bool blocking,
|
||||||
|
QIOChannel *src,
|
||||||
|
QIOChannel *dst);
|
||||||
|
|
||||||
|
void qio_channel_test_run_writer(QIOChannelTest *test,
|
||||||
|
QIOChannel *src);
|
||||||
|
void qio_channel_test_run_reader(QIOChannelTest *test,
|
||||||
|
QIOChannel *dst);
|
||||||
|
|
||||||
|
void qio_channel_test_validate(QIOChannelTest *test);
|
||||||
|
|
||||||
|
#endif /* TEST_IO_CHANNEL_HELPERS */
|
399
tests/test-io-channel-socket.c
Normal file
399
tests/test-io-channel-socket.c
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
gaierr = getaddrinfo("::1", NULL, &hints, &ai);
|
||||||
|
if (gaierr != 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->type == SOCKET_ADDRESS_KIND_INET) {
|
||||||
|
SocketAddress *laddr = qio_channel_socket_get_local_address(
|
||||||
|
lioc, &error_abort);
|
||||||
|
|
||||||
|
g_free(connect_addr->u.inet->port);
|
||||||
|
connect_addr->u.inet->port = g_strdup(laddr->u.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_set_delay(*src, false);
|
||||||
|
|
||||||
|
*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_main_context_iteration(g_main_context_default(), FALSE);
|
||||||
|
|
||||||
|
g_assert(!data.err);
|
||||||
|
|
||||||
|
if (listen_addr->type == SOCKET_ADDRESS_KIND_INET) {
|
||||||
|
SocketAddress *laddr = qio_channel_socket_get_local_address(
|
||||||
|
lioc, &error_abort);
|
||||||
|
|
||||||
|
g_free(connect_addr->u.inet->port);
|
||||||
|
connect_addr->u.inet->port = g_strdup(laddr->u.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_main_context_iteration(g_main_context_default(), FALSE);
|
||||||
|
|
||||||
|
g_assert(!data.err);
|
||||||
|
|
||||||
|
*dst = QIO_CHANNEL(qio_channel_socket_accept(lioc, &error_abort));
|
||||||
|
g_assert(*dst);
|
||||||
|
|
||||||
|
qio_channel_set_delay(*src, false);
|
||||||
|
test_io_channel_set_socket_bufs(*src, *dst);
|
||||||
|
|
||||||
|
object_unref(OBJECT(lioc));
|
||||||
|
|
||||||
|
g_main_loop_unref(data.loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void test_io_channel(bool async,
|
||||||
|
SocketAddress *listen_addr,
|
||||||
|
SocketAddress *connect_addr)
|
||||||
|
{
|
||||||
|
QIOChannel *src, *dst;
|
||||||
|
QIOChannelTest *test;
|
||||||
|
if (async) {
|
||||||
|
test_io_channel_setup_async(listen_addr, connect_addr, &src, &dst);
|
||||||
|
|
||||||
|
test = qio_channel_test_new();
|
||||||
|
qio_channel_test_run_threads(test, true, src, dst);
|
||||||
|
qio_channel_test_validate(test);
|
||||||
|
|
||||||
|
object_unref(OBJECT(src));
|
||||||
|
object_unref(OBJECT(dst));
|
||||||
|
|
||||||
|
test_io_channel_setup_async(listen_addr, connect_addr, &src, &dst);
|
||||||
|
|
||||||
|
test = qio_channel_test_new();
|
||||||
|
qio_channel_test_run_threads(test, false, src, dst);
|
||||||
|
qio_channel_test_validate(test);
|
||||||
|
|
||||||
|
object_unref(OBJECT(src));
|
||||||
|
object_unref(OBJECT(dst));
|
||||||
|
} else {
|
||||||
|
test_io_channel_setup_sync(listen_addr, connect_addr, &src, &dst);
|
||||||
|
|
||||||
|
test = qio_channel_test_new();
|
||||||
|
qio_channel_test_run_threads(test, true, src, dst);
|
||||||
|
qio_channel_test_validate(test);
|
||||||
|
|
||||||
|
object_unref(OBJECT(src));
|
||||||
|
object_unref(OBJECT(dst));
|
||||||
|
|
||||||
|
test_io_channel_setup_sync(listen_addr, connect_addr, &src, &dst);
|
||||||
|
|
||||||
|
test = qio_channel_test_new();
|
||||||
|
qio_channel_test_run_threads(test, false, src, dst);
|
||||||
|
qio_channel_test_validate(test);
|
||||||
|
|
||||||
|
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->type = SOCKET_ADDRESS_KIND_INET;
|
||||||
|
listen_addr->u.inet = g_new0(InetSocketAddress, 1);
|
||||||
|
listen_addr->u.inet->host = g_strdup("0.0.0.0");
|
||||||
|
listen_addr->u.inet->port = NULL; /* Auto-select */
|
||||||
|
|
||||||
|
connect_addr->type = SOCKET_ADDRESS_KIND_INET;
|
||||||
|
connect_addr->u.inet = g_new0(InetSocketAddress, 1);
|
||||||
|
connect_addr->u.inet->host = g_strdup("127.0.0.1");
|
||||||
|
connect_addr->u.inet->port = NULL; /* Filled in later */
|
||||||
|
|
||||||
|
test_io_channel(async, listen_addr, connect_addr);
|
||||||
|
|
||||||
|
qapi_free_SocketAddress(listen_addr);
|
||||||
|
qapi_free_SocketAddress(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->type = SOCKET_ADDRESS_KIND_INET;
|
||||||
|
listen_addr->u.inet = g_new0(InetSocketAddress, 1);
|
||||||
|
listen_addr->u.inet->host = g_strdup("::");
|
||||||
|
listen_addr->u.inet->port = NULL; /* Auto-select */
|
||||||
|
|
||||||
|
connect_addr->type = SOCKET_ADDRESS_KIND_INET;
|
||||||
|
connect_addr->u.inet = g_new0(InetSocketAddress, 1);
|
||||||
|
connect_addr->u.inet->host = g_strdup("::1");
|
||||||
|
connect_addr->u.inet->port = NULL; /* Filled in later */
|
||||||
|
|
||||||
|
test_io_channel(async, listen_addr, connect_addr);
|
||||||
|
|
||||||
|
qapi_free_SocketAddress(listen_addr);
|
||||||
|
qapi_free_SocketAddress(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);
|
||||||
|
|
||||||
|
#define TEST_SOCKET "test-io-channel-socket.sock"
|
||||||
|
listen_addr->type = SOCKET_ADDRESS_KIND_UNIX;
|
||||||
|
listen_addr->u.q_unix = g_new0(UnixSocketAddress, 1);
|
||||||
|
listen_addr->u.q_unix->path = g_strdup(TEST_SOCKET);
|
||||||
|
|
||||||
|
connect_addr->type = SOCKET_ADDRESS_KIND_UNIX;
|
||||||
|
connect_addr->u.q_unix = g_new0(UnixSocketAddress, 1);
|
||||||
|
connect_addr->u.q_unix->path = g_strdup(TEST_SOCKET);
|
||||||
|
|
||||||
|
test_io_channel(async, listen_addr, connect_addr);
|
||||||
|
|
||||||
|
qapi_free_SocketAddress(listen_addr);
|
||||||
|
qapi_free_SocketAddress(connect_addr);
|
||||||
|
unlink(TEST_SOCKET);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
/* We're creating actual IPv4/6 sockets, so we should
|
||||||
|
* check if the host running tests actually supports
|
||||||
|
* each protocol to avoid breaking tests on machines
|
||||||
|
* with either IPv4 or IPv6 disabled.
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
}
|
19
trace-events
19
trace-events
@ -1817,3 +1817,22 @@ qio_task_thread_start(void *task, void *worker, void *opaque) "Task thread start
|
|||||||
qio_task_thread_run(void *task) "Task thread run task=%p"
|
qio_task_thread_run(void *task) "Task thread run task=%p"
|
||||||
qio_task_thread_exit(void *task) "Task thread exit task=%p"
|
qio_task_thread_exit(void *task) "Task thread exit task=%p"
|
||||||
qio_task_thread_result(void *task) "Task thread result task=%p"
|
qio_task_thread_result(void *task) "Task thread result task=%p"
|
||||||
|
|
||||||
|
# io/channel-socket.c
|
||||||
|
qio_channel_socket_new(void *ioc) "Socket new ioc=%p"
|
||||||
|
qio_channel_socket_new_fd(void *ioc, int fd) "Socket new ioc=%p fd=%d"
|
||||||
|
qio_channel_socket_connect_sync(void *ioc, void *addr) "Socket connect sync ioc=%p addr=%p"
|
||||||
|
qio_channel_socket_connect_async(void *ioc, void *addr) "Socket connect async ioc=%p addr=%p"
|
||||||
|
qio_channel_socket_connect_fail(void *ioc) "Socket connect fail ioc=%p"
|
||||||
|
qio_channel_socket_connect_complete(void *ioc, int fd) "Socket connect complete ioc=%p fd=%d"
|
||||||
|
qio_channel_socket_listen_sync(void *ioc, void *addr) "Socket listen sync ioc=%p addr=%p"
|
||||||
|
qio_channel_socket_listen_async(void *ioc, void *addr) "Socket listen async ioc=%p addr=%p"
|
||||||
|
qio_channel_socket_listen_fail(void *ioc) "Socket listen fail ioc=%p"
|
||||||
|
qio_channel_socket_listen_complete(void *ioc, int fd) "Socket listen complete ioc=%p fd=%d"
|
||||||
|
qio_channel_socket_dgram_sync(void *ioc, void *localAddr, void *remoteAddr) "Socket dgram sync ioc=%p localAddr=%p remoteAddr=%p"
|
||||||
|
qio_channel_socket_dgram_async(void *ioc, void *localAddr, void *remoteAddr) "Socket dgram async ioc=%p localAddr=%p remoteAddr=%p"
|
||||||
|
qio_channel_socket_dgram_fail(void *ioc) "Socket dgram fail ioc=%p"
|
||||||
|
qio_channel_socket_dgram_complete(void *ioc, int fd) "Socket dgram complete ioc=%p fd=%d"
|
||||||
|
qio_channel_socket_accept(void *ioc) "Socket accept start ioc=%p"
|
||||||
|
qio_channel_socket_accept_fail(void *ioc) "Socket accept fail ioc=%p"
|
||||||
|
qio_channel_socket_accept_complete(void *ioc, void *cioc, int fd) "Socket accept complete ioc=%p cioc=%p fd=%d"
|
||||||
|
@ -1086,7 +1086,7 @@ socket_sockaddr_to_address_unix(struct sockaddr_storage *sa,
|
|||||||
}
|
}
|
||||||
#endif /* WIN32 */
|
#endif /* WIN32 */
|
||||||
|
|
||||||
static SocketAddress *
|
SocketAddress *
|
||||||
socket_sockaddr_to_address(struct sockaddr_storage *sa,
|
socket_sockaddr_to_address(struct sockaddr_storage *sa,
|
||||||
socklen_t salen,
|
socklen_t salen,
|
||||||
Error **errp)
|
Error **errp)
|
||||||
|
Loading…
Reference in New Issue
Block a user