#include <glib/gstdio.h>
#include "qemu/config-file.h"
+#include "qemu/module.h"
#include "qemu/option.h"
#include "qemu/sockets.h"
#include "chardev/char-fe.h"
-#include "chardev/char-mux.h"
#include "sysemu/sysemu.h"
#include "qapi/error.h"
#include "qapi/qapi-commands-char.h"
#include "io/channel-socket.h"
#include "qapi/qobject-input-visitor.h"
#include "qapi/qapi-visit-sockets.h"
+#include "socket-helpers.h"
static bool quit;
quit = true;
}
-static void fe_event(void *opaque, int event)
+static void fe_event(void *opaque, QEMUChrEvent event)
{
FeHandler *h = opaque;
bool new_open_state;
1, &error_abort);
qemu_opt_set(opts, "backend", "console", &error_abort);
- chr = qemu_chr_new_from_opts(opts, NULL);
+ chr = qemu_chr_new_from_opts(opts, NULL, NULL);
g_assert_nonnull(chr);
qemu_chr_write_all(chr, (const uint8_t *)"CONSOLE", 7);
CharBackend be;
int ret;
- chr = qemu_chr_new("label", "stdio");
+ chr = qemu_chr_new("label", "stdio", NULL);
g_assert_nonnull(chr);
qemu_chr_fe_init(&be, chr, &error_abort);
qemu_opt_set(opts, "backend", "ringbuf", &error_abort);
qemu_opt_set(opts, "size", "5", &error_abort);
- chr = qemu_chr_new_from_opts(opts, NULL);
+ chr = qemu_chr_new_from_opts(opts, NULL, NULL);
g_assert_null(chr);
qemu_opts_del(opts);
1, &error_abort);
qemu_opt_set(opts, "backend", "ringbuf", &error_abort);
qemu_opt_set(opts, "size", "2", &error_abort);
- chr = qemu_chr_new_from_opts(opts, &error_abort);
+ chr = qemu_chr_new_from_opts(opts, NULL, &error_abort);
g_assert_nonnull(chr);
qemu_opts_del(opts);
1, &error_abort);
qemu_opt_set(opts, "backend", "memory", &error_abort);
qemu_opt_set(opts, "size", "2", &error_abort);
- chr = qemu_chr_new_from_opts(opts, NULL);
+ chr = qemu_chr_new_from_opts(opts, NULL, NULL);
g_assert_nonnull(chr);
object_unparent(OBJECT(chr));
qemu_opts_del(opts);
qemu_opt_set(opts, "backend", "ringbuf", &error_abort);
qemu_opt_set(opts, "size", "128", &error_abort);
qemu_opt_set(opts, "mux", "on", &error_abort);
- chr = qemu_chr_new_from_opts(opts, &error_abort);
+ chr = qemu_chr_new_from_opts(opts, NULL, &error_abort);
g_assert_nonnull(chr);
qemu_opts_del(opts);
CharBackend client_be;
Chardev *chr_client;
Chardev *chr = qemu_chr_new("server",
- "websocket:127.0.0.1:0,server,nowait");
+ "websocket:127.0.0.1:0,server,nowait", NULL);
const char handshake[] = "GET / HTTP/1.1\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
qemu_chr_fe_set_handlers(&be, websock_server_can_read, websock_server_read,
NULL, NULL, chr, NULL, true);
- chr_client = qemu_chr_new("client", tmp);
+ chr_client = qemu_chr_new("client", tmp, NULL);
qemu_chr_fe_init(&client_be, chr_client, &error_abort);
qemu_chr_fe_set_handlers(&client_be, websock_client_can_read,
websock_client_read,
}
tmp = g_strdup_printf("pipe:%s", pipe);
- chr = qemu_chr_new("pipe", tmp);
+ chr = qemu_chr_new("pipe", tmp, NULL);
g_assert_nonnull(chr);
g_free(tmp);
int port;
sock = make_udp_socket(&port);
tmp = g_strdup_printf("udp:127.0.0.1:%d", port);
- chr = qemu_chr_new("client", tmp);
+ chr = qemu_chr_new("client", tmp, NULL);
g_assert_nonnull(chr);
be = g_alloca(sizeof(CharBackend));
typedef struct {
int event;
bool got_pong;
+ CharBackend *be;
} CharSocketTestData;
#define SOCKET_PING "Hello"
#define SOCKET_PONG "World"
+typedef void (*char_socket_cb)(void *opaque, QEMUChrEvent event);
static void
-char_socket_event(void *opaque, int event)
+char_socket_event(void *opaque, QEMUChrEvent event)
{
CharSocketTestData *data = opaque;
data->event = event;
}
+static void
+char_socket_event_with_error(void *opaque, QEMUChrEvent event)
+{
+ static bool first_error;
+ CharSocketTestData *data = opaque;
+ CharBackend *be = data->be;
+ data->event = event;
+ switch (event) {
+ case CHR_EVENT_OPENED:
+ if (!first_error) {
+ first_error = true;
+ qemu_chr_fe_disconnect(be);
+ }
+ return;
+ case CHR_EVENT_CLOSED:
+ return;
+ default:
+ return;
+ }
+}
+
static void
char_socket_read(void *opaque, const uint8_t *buf, int size)
char *optstr;
g_assert(!reconnect);
if (is_listen) {
- qio_channel_socket_listen_sync(ioc, addr, &error_abort);
+ qio_channel_socket_listen_sync(ioc, addr, 1, &error_abort);
} else {
qio_channel_socket_connect_sync(ioc, addr, &error_abort);
}
}
-static void
-char_socket_ping_pong(QIOChannel *ioc)
+static int
+char_socket_ping_pong(QIOChannel *ioc, Error **errp)
{
char greeting[sizeof(SOCKET_PING)];
const char *response = SOCKET_PONG;
- qio_channel_read_all(ioc, greeting, sizeof(greeting), &error_abort);
+ int ret;
+ ret = qio_channel_read_all(ioc, greeting, sizeof(greeting), errp);
+ if (ret != 0) {
+ object_unref(OBJECT(ioc));
+ return -1;
+ }
g_assert(memcmp(greeting, SOCKET_PING, sizeof(greeting)) == 0);
- qio_channel_write_all(ioc, response, sizeof(SOCKET_PONG), &error_abort);
-
+ qio_channel_write_all(ioc, response, sizeof(SOCKET_PONG), errp);
object_unref(OBJECT(ioc));
+ return 0;
}
qio_channel_socket_connect_sync(ioc, addr, &error_abort);
- char_socket_ping_pong(QIO_CHANNEL(ioc));
+ char_socket_ping_pong(QIO_CHANNEL(ioc), &error_abort);
return NULL;
}
Visitor *v;
QemuThread thread;
int ret;
- bool reconnected;
+ bool reconnected = false;
char *optstr;
QemuOpts *opts;
opts = qemu_opts_parse_noisily(qemu_find_opts("chardev"),
optstr, true);
g_assert_nonnull(opts);
- chr = qemu_chr_new_from_opts(opts, &error_abort);
+ chr = qemu_chr_new_from_opts(opts, NULL, &error_abort);
qemu_opts_del(opts);
g_assert_nonnull(chr);
g_assert(!object_property_get_bool(OBJECT(chr), "connected", &error_abort));
reconnect:
data.event = -1;
+ data.be = &be;
qemu_chr_fe_set_handlers(&be, NULL, NULL,
char_socket_event, NULL,
&data, NULL, true);
QIOChannelSocket *ioc = data;
QIOChannelSocket *cioc;
+retry:
cioc = qio_channel_socket_accept(ioc, &error_abort);
g_assert_nonnull(cioc);
- char_socket_ping_pong(QIO_CHANNEL(cioc));
+ if (char_socket_ping_pong(QIO_CHANNEL(cioc), NULL) != 0) {
+ goto retry;
+ }
return NULL;
}
const char *reconnect;
bool wait_connected;
bool fd_pass;
+ char_socket_cb event_cb;
} CharSocketClientTestConfig;
+static void char_socket_client_dupid_test(gconstpointer opaque)
+{
+ const CharSocketClientTestConfig *config = opaque;
+ QIOChannelSocket *ioc;
+ char *optstr;
+ Chardev *chr1, *chr2;
+ SocketAddress *addr;
+ QemuOpts *opts;
+ Error *local_err = NULL;
+
+ /*
+ * Setup a listener socket and determine get its address
+ * so we know the TCP port for the client later
+ */
+ ioc = qio_channel_socket_new();
+ g_assert_nonnull(ioc);
+ qio_channel_socket_listen_sync(ioc, config->addr, 1, &error_abort);
+ addr = qio_channel_socket_get_local_address(ioc, &error_abort);
+ g_assert_nonnull(addr);
+
+ /*
+ * Populate the chardev address based on what the server
+ * is actually listening on
+ */
+ optstr = char_socket_addr_to_opt_str(addr,
+ config->fd_pass,
+ config->reconnect,
+ false);
+
+ opts = qemu_opts_parse_noisily(qemu_find_opts("chardev"),
+ optstr, true);
+ g_assert_nonnull(opts);
+ chr1 = qemu_chr_new_from_opts(opts, NULL, &error_abort);
+ g_assert_nonnull(chr1);
+
+ chr2 = qemu_chr_new_from_opts(opts, NULL, &local_err);
+ g_assert_null(chr2);
+ error_free_or_abort(&local_err);
+
+ object_unref(OBJECT(ioc));
+ qemu_opts_del(opts);
+ object_unparent(OBJECT(chr1));
+ qapi_free_SocketAddress(addr);
+ g_free(optstr);
+}
static void char_socket_client_test(gconstpointer opaque)
{
const CharSocketClientTestConfig *config = opaque;
+ const char_socket_cb event_cb = config->event_cb;
QIOChannelSocket *ioc;
char *optstr;
Chardev *chr;
*/
ioc = qio_channel_socket_new();
g_assert_nonnull(ioc);
- qio_channel_socket_listen_sync(ioc, config->addr, &error_abort);
+ qio_channel_socket_listen_sync(ioc, config->addr, 1, &error_abort);
addr = qio_channel_socket_get_local_address(ioc, &error_abort);
g_assert_nonnull(addr);
opts = qemu_opts_parse_noisily(qemu_find_opts("chardev"),
optstr, true);
g_assert_nonnull(opts);
- chr = qemu_chr_new_from_opts(opts, &error_abort);
+ chr = qemu_chr_new_from_opts(opts, NULL, &error_abort);
qemu_opts_del(opts);
g_assert_nonnull(chr);
reconnect:
data.event = -1;
+ data.be = &be;
qemu_chr_fe_set_handlers(&be, NULL, NULL,
- char_socket_event, NULL,
+ event_cb, NULL,
&data, NULL, true);
if (config->reconnect) {
g_assert(data.event == -1);
/* Setup a callback to receive the reply to our greeting */
qemu_chr_fe_set_handlers(&be, char_socket_can_read,
char_socket_read,
- char_socket_event, NULL,
+ event_cb, NULL,
&data, NULL, true);
g_assert(data.event == CHR_EVENT_OPENED);
data.event = -1;
g_free(optstr);
}
+static void
+count_closed_event(void *opaque, QEMUChrEvent event)
+{
+ int *count = opaque;
+ if (event == CHR_EVENT_CLOSED) {
+ (*count)++;
+ }
+}
-#ifdef HAVE_CHARDEV_SERIAL
+static void
+char_socket_discard_read(void *opaque, const uint8_t *buf, int size)
+{
+}
+
+static void char_socket_server_two_clients_test(gconstpointer opaque)
+{
+ SocketAddress *incoming_addr = (gpointer) opaque;
+ Chardev *chr;
+ CharBackend be = {0};
+ QObject *qaddr;
+ SocketAddress *addr;
+ Visitor *v;
+ char *optstr;
+ QemuOpts *opts;
+ QIOChannelSocket *ioc1, *ioc2;
+ int closed = 0;
+
+ g_setenv("QTEST_SILENT_ERRORS", "1", 1);
+ /*
+ * We rely on addr containing "nowait", otherwise
+ * qemu_chr_new() will block until a client connects. We
+ * can't spawn our client thread though, because until
+ * qemu_chr_new() returns we don't know what TCP port was
+ * allocated by the OS
+ */
+ optstr = char_socket_addr_to_opt_str(incoming_addr,
+ false,
+ NULL,
+ true);
+ opts = qemu_opts_parse_noisily(qemu_find_opts("chardev"),
+ optstr, true);
+ g_assert_nonnull(opts);
+ chr = qemu_chr_new_from_opts(opts, NULL, &error_abort);
+ qemu_opts_del(opts);
+ g_assert_nonnull(chr);
+ g_assert(!object_property_get_bool(OBJECT(chr), "connected", &error_abort));
+
+ qaddr = object_property_get_qobject(OBJECT(chr), "addr", &error_abort);
+ g_assert_nonnull(qaddr);
+
+ v = qobject_input_visitor_new(qaddr);
+ visit_type_SocketAddress(v, "addr", &addr, &error_abort);
+ visit_free(v);
+ qobject_unref(qaddr);
+
+ qemu_chr_fe_init(&be, chr, &error_abort);
+
+ qemu_chr_fe_set_handlers(&be, char_socket_can_read, char_socket_discard_read,
+ count_closed_event, NULL,
+ &closed, NULL, true);
+
+ ioc1 = qio_channel_socket_new();
+ qio_channel_socket_connect_sync(ioc1, addr, &error_abort);
+ qemu_chr_wait_connected(chr, &error_abort);
+
+ /* switch the chardev to another context */
+ GMainContext *ctx = g_main_context_new();
+ qemu_chr_fe_set_handlers(&be, char_socket_can_read, char_socket_discard_read,
+ count_closed_event, NULL,
+ &closed, ctx, true);
+
+ /* Start a second connection while the first is still connected.
+ * It will be placed in the listen() backlog, and connect() will
+ * succeed immediately.
+ */
+ ioc2 = qio_channel_socket_new();
+ qio_channel_socket_connect_sync(ioc2, addr, &error_abort);
+
+ object_unref(OBJECT(ioc1));
+ /* The two connections should now be processed serially. */
+ while (g_main_context_iteration(ctx, TRUE)) {
+ if (closed == 1 && ioc2) {
+ object_unref(OBJECT(ioc2));
+ ioc2 = NULL;
+ }
+ if (closed == 2) {
+ break;
+ }
+ }
+
+ qapi_free_SocketAddress(addr);
+ object_unparent(OBJECT(chr));
+ g_main_context_unref(ctx);
+ g_free(optstr);
+ g_unsetenv("QTEST_SILENT_ERRORS");
+}
+
+
+#if defined(HAVE_CHARDEV_SERIAL) && !defined(WIN32)
static void char_serial_test(void)
{
QemuOpts *opts;
qemu_opt_set(opts, "backend", "serial", &error_abort);
qemu_opt_set(opts, "path", "/dev/null", &error_abort);
- chr = qemu_chr_new_from_opts(opts, NULL);
+ chr = qemu_chr_new_from_opts(opts, NULL, NULL);
g_assert_nonnull(chr);
/* TODO: add more tests with a pty */
object_unparent(OBJECT(chr));
/* test tty alias */
qemu_opt_set(opts, "backend", "tty", &error_abort);
- chr = qemu_chr_new_from_opts(opts, NULL);
+ chr = qemu_chr_new_from_opts(opts, NULL, &error_abort);
g_assert_nonnull(chr);
object_unparent(OBJECT(chr));
g_assert_cmpint(ret, ==, 8);
chr = qemu_chardev_new("label-file", TYPE_CHARDEV_FILE, &backend,
- &error_abort);
+ NULL, &error_abort);
qemu_chr_fe_init(&be, chr, &error_abort);
qemu_chr_fe_set_handlers(&be,
out = g_build_filename(tmp_path, "out", NULL);
file.out = out;
chr = qemu_chardev_new(NULL, TYPE_CHARDEV_FILE, &backend,
- &error_abort);
+ NULL, &error_abort);
}
ret = qemu_chr_write_all(chr, (uint8_t *)"hello!", 6);
g_assert_cmpint(ret, ==, 6);
chr = qemu_chr_find("label-null");
g_assert_null(chr);
- chr = qemu_chr_new("label-null", "null");
+ chr = qemu_chr_new("label-null", "null", NULL);
chr = qemu_chr_find("label-null");
g_assert_nonnull(chr);
{
Chardev *chr;
g_setenv("QTEST_SILENT_ERRORS", "1", 1);
- chr = qemu_chr_new("label-invalid", "invalid");
+ chr = qemu_chr_new("label-invalid", "invalid", NULL);
g_assert_null(chr);
g_unsetenv("QTEST_SILENT_ERRORS");
}
chr_args = g_strdup_printf("udp:127.0.0.1:%d", port);
- chr = qemu_chr_new("chardev", chr_args);
+ chr = qemu_chr_new("chardev", chr_args, NULL);
qemu_chr_fe_init(&be, chr, &error_abort);
/* check that chardev operates correctly */
g_free(chr_args);
}
+static SocketAddress tcpaddr = {
+ .type = SOCKET_ADDRESS_TYPE_INET,
+ .u.inet.host = (char *)"127.0.0.1",
+ .u.inet.port = (char *)"0",
+};
+#ifndef WIN32
+static SocketAddress unixaddr = {
+ .type = SOCKET_ADDRESS_TYPE_UNIX,
+ .u.q_unix.path = (char *)"test-char.sock",
+};
+#endif
+
int main(int argc, char **argv)
{
+ bool has_ipv4, has_ipv6;
+
qemu_init_main_loop(&error_abort);
socket_init();
g_test_init(&argc, &argv, NULL);
+ if (socket_check_protocol_support(&has_ipv4, &has_ipv6) < 0) {
+ g_printerr("socket_check_protocol_support() failed\n");
+ goto end;
+ }
+
module_call_init(MODULE_INIT_QOM);
qemu_add_opts(&qemu_chardev_opts);
g_test_add_func("/char/file-fifo", char_file_fifo_test);
#endif
- SocketAddress tcpaddr = {
- .type = SOCKET_ADDRESS_TYPE_INET,
- .u.inet.host = (char *)"127.0.0.1",
- .u.inet.port = (char *)"0",
- };
-#ifndef WIN32
- SocketAddress unixaddr = {
- .type = SOCKET_ADDRESS_TYPE_UNIX,
- .u.q_unix.path = (char *)"test-char.sock",
- };
-#endif
-
#define SOCKET_SERVER_TEST(name, addr) \
- CharSocketServerTestConfig server1 ## name = \
+ static CharSocketServerTestConfig server1 ## name = \
{ addr, false, false }; \
- CharSocketServerTestConfig server2 ## name = \
+ static CharSocketServerTestConfig server2 ## name = \
{ addr, true, false }; \
- CharSocketServerTestConfig server3 ## name = \
+ static CharSocketServerTestConfig server3 ## name = \
{ addr, false, true }; \
- CharSocketServerTestConfig server4 ## name = \
+ static CharSocketServerTestConfig server4 ## name = \
{ addr, true, true }; \
g_test_add_data_func("/char/socket/server/mainloop/" # name, \
&server1 ##name, char_socket_server_test); \
&server4 ##name, char_socket_server_test)
#define SOCKET_CLIENT_TEST(name, addr) \
- CharSocketClientTestConfig client1 ## name = \
- { addr, NULL, false, false }; \
- CharSocketClientTestConfig client2 ## name = \
- { addr, NULL, true, false }; \
- CharSocketClientTestConfig client3 ## name = \
- { addr, ",reconnect=1", false }; \
- CharSocketClientTestConfig client4 ## name = \
- { addr, ",reconnect=1", true }; \
- CharSocketClientTestConfig client5 ## name = \
- { addr, NULL, false, true }; \
- CharSocketClientTestConfig client6 ## name = \
- { addr, NULL, true, true }; \
+ static CharSocketClientTestConfig client1 ## name = \
+ { addr, NULL, false, false, char_socket_event }; \
+ static CharSocketClientTestConfig client2 ## name = \
+ { addr, NULL, true, false, char_socket_event }; \
+ static CharSocketClientTestConfig client3 ## name = \
+ { addr, ",reconnect=1", false, false, char_socket_event }; \
+ static CharSocketClientTestConfig client4 ## name = \
+ { addr, ",reconnect=1", true, false, char_socket_event }; \
+ static CharSocketClientTestConfig client5 ## name = \
+ { addr, NULL, false, true, char_socket_event }; \
+ static CharSocketClientTestConfig client6 ## name = \
+ { addr, NULL, true, true, char_socket_event }; \
+ static CharSocketClientTestConfig client7 ## name = \
+ { addr, ",reconnect=1", true, false, \
+ char_socket_event_with_error }; \
+ static CharSocketClientTestConfig client8 ## name = \
+ { addr, ",reconnect=1", false, false, char_socket_event }; \
g_test_add_data_func("/char/socket/client/mainloop/" # name, \
&client1 ##name, char_socket_client_test); \
g_test_add_data_func("/char/socket/client/wait-conn/" # name, \
g_test_add_data_func("/char/socket/client/mainloop-fdpass/" # name, \
&client5 ##name, char_socket_client_test); \
g_test_add_data_func("/char/socket/client/wait-conn-fdpass/" # name, \
- &client6 ##name, char_socket_client_test)
-
- SOCKET_SERVER_TEST(tcp, &tcpaddr);
- SOCKET_CLIENT_TEST(tcp, &tcpaddr);
+ &client6 ##name, char_socket_client_test); \
+ g_test_add_data_func("/char/socket/client/reconnect-error/" # name, \
+ &client7 ##name, char_socket_client_test); \
+ g_test_add_data_func("/char/socket/client/dupid-reconnect/" # name, \
+ &client8 ##name, char_socket_client_dupid_test)
+
+ if (has_ipv4) {
+ SOCKET_SERVER_TEST(tcp, &tcpaddr);
+ SOCKET_CLIENT_TEST(tcp, &tcpaddr);
+ g_test_add_data_func("/char/socket/server/two-clients/tcp", &tcpaddr,
+ char_socket_server_two_clients_test);
+ }
#ifndef WIN32
SOCKET_SERVER_TEST(unix, &unixaddr);
SOCKET_CLIENT_TEST(unix, &unixaddr);
+ g_test_add_data_func("/char/socket/server/two-clients/unix", &unixaddr,
+ char_socket_server_two_clients_test);
#endif
-
g_test_add_func("/char/udp", char_udp_test);
-#ifdef HAVE_CHARDEV_SERIAL
+#if defined(HAVE_CHARDEV_SERIAL) && !defined(WIN32)
g_test_add_func("/char/serial", char_serial_test);
#endif
g_test_add_func("/char/hotswap", char_hotswap_test);
g_test_add_func("/char/websocket", char_websock_test);
+end:
return g_test_run();
}