mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-02 07:13:54 -06:00

When running 'info network', if the stream backend is still in the process of connecting, or waiting for an incoming connection, no information is displayed. There is also no way to distinguish whether the server is still in the process of setting up the listener socket, or whether it is ready to accept incoming client connections. This leads to a race condition in the netdev-socket qtest which launches a server process followed by a client process. Under high load conditions it is possible for the client to attempt to connect before the server is accepting clients. For the scenarios which do not set the 'reconnect' option, this opens up a race which can lead to the test scenario failing to reach the expected state. Now that 'info network' can distinguish between initialization phase and the listening phase, the netdev-socket qtest will correctly synchronize, such that the client QEMU is not spawned until the server is ready. This should solve the non-deterministic failures seen with the netdev-socket qtest. Signed-off-by: "Daniel P. Berrangé" <berrange@redhat.com> Message-ID: <20240104162942.211458-5-berrange@redhat.com> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> Signed-off-by: Thomas Huth <thuth@redhat.com>
548 lines
15 KiB
C
548 lines
15 KiB
C
/*
|
|
* QTest testcase for netdev stream and dgram
|
|
*
|
|
* Copyright (c) 2022 Red Hat, Inc.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/sockets.h"
|
|
#include <glib/gstdio.h>
|
|
#include "../unit/socket-helpers.h"
|
|
#include "libqtest.h"
|
|
#include "qapi/qmp/qstring.h"
|
|
#include "qemu/sockets.h"
|
|
#include "qapi/qobject-input-visitor.h"
|
|
#include "qapi/qapi-visit-sockets.h"
|
|
|
|
#define CONNECTION_TIMEOUT 60
|
|
|
|
#define EXPECT_STATE(q, e, t) \
|
|
do { \
|
|
char *resp = NULL; \
|
|
g_test_timer_start(); \
|
|
do { \
|
|
g_free(resp); \
|
|
resp = qtest_hmp(q, "info network"); \
|
|
if (t) { \
|
|
strrchr(resp, t)[0] = 0; \
|
|
} \
|
|
if (g_str_equal(resp, e)) { \
|
|
break; \
|
|
} \
|
|
} while (g_test_timer_elapsed() < CONNECTION_TIMEOUT); \
|
|
g_assert_cmpstr(resp, ==, e); \
|
|
g_free(resp); \
|
|
} while (0)
|
|
|
|
static gchar *tmpdir;
|
|
|
|
static int inet_get_free_port_socket_ipv4(int sock)
|
|
{
|
|
struct sockaddr_in addr;
|
|
socklen_t len;
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
addr.sin_port = 0;
|
|
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
len = sizeof(addr);
|
|
if (getsockname(sock, (struct sockaddr *)&addr, &len) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return ntohs(addr.sin_port);
|
|
}
|
|
|
|
static int inet_get_free_port_socket_ipv6(int sock)
|
|
{
|
|
struct sockaddr_in6 addr;
|
|
socklen_t len;
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin6_family = AF_INET6;
|
|
addr.sin6_addr = in6addr_any;
|
|
addr.sin6_port = 0;
|
|
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
len = sizeof(addr);
|
|
if (getsockname(sock, (struct sockaddr *)&addr, &len) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return ntohs(addr.sin6_port);
|
|
}
|
|
|
|
static int inet_get_free_port_multiple(int nb, int *port, bool ipv6)
|
|
{
|
|
g_autofree int *sock = g_new(int, nb);
|
|
int i;
|
|
|
|
for (i = 0; i < nb; i++) {
|
|
sock[i] = socket(ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0);
|
|
if (sock[i] < 0) {
|
|
break;
|
|
}
|
|
port[i] = ipv6 ? inet_get_free_port_socket_ipv6(sock[i]) :
|
|
inet_get_free_port_socket_ipv4(sock[i]);
|
|
if (port[i] == -1) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
nb = i;
|
|
for (i = 0; i < nb; i++) {
|
|
close(sock[i]);
|
|
}
|
|
|
|
return nb;
|
|
}
|
|
|
|
static int inet_get_free_port(bool ipv6)
|
|
{
|
|
int nb, port;
|
|
|
|
nb = inet_get_free_port_multiple(1, &port, ipv6);
|
|
g_assert_cmpint(nb, ==, 1);
|
|
|
|
return port;
|
|
}
|
|
|
|
static void test_stream_inet_ipv4(void)
|
|
{
|
|
QTestState *qts0, *qts1;
|
|
char *expect;
|
|
int port;
|
|
|
|
port = inet_get_free_port(false);
|
|
qts0 = qtest_initf("-nodefaults -M none "
|
|
"-netdev stream,id=st0,server=true,addr.type=inet,"
|
|
"addr.ipv4=on,addr.ipv6=off,"
|
|
"addr.host=127.0.0.1,addr.port=%d", port);
|
|
|
|
EXPECT_STATE(qts0, "st0: index=0,type=stream,listening\r\n", 0);
|
|
|
|
qts1 = qtest_initf("-nodefaults -M none "
|
|
"-netdev stream,server=false,id=st0,addr.type=inet,"
|
|
"addr.ipv4=on,addr.ipv6=off,"
|
|
"addr.host=127.0.0.1,addr.port=%d", port);
|
|
|
|
expect = g_strdup_printf("st0: index=0,type=stream,tcp:127.0.0.1:%d\r\n",
|
|
port);
|
|
EXPECT_STATE(qts1, expect, 0);
|
|
g_free(expect);
|
|
|
|
/* the port is unknown, check only the address */
|
|
EXPECT_STATE(qts0, "st0: index=0,type=stream,tcp:127.0.0.1", ':');
|
|
|
|
qtest_quit(qts1);
|
|
qtest_quit(qts0);
|
|
}
|
|
|
|
static void wait_stream_connected(QTestState *qts, const char *id,
|
|
SocketAddress **addr)
|
|
{
|
|
QDict *resp, *data;
|
|
QString *qstr;
|
|
QObject *obj;
|
|
Visitor *v = NULL;
|
|
|
|
resp = qtest_qmp_eventwait_ref(qts, "NETDEV_STREAM_CONNECTED");
|
|
g_assert_nonnull(resp);
|
|
data = qdict_get_qdict(resp, "data");
|
|
g_assert_nonnull(data);
|
|
|
|
qstr = qobject_to(QString, qdict_get(data, "netdev-id"));
|
|
g_assert_nonnull(data);
|
|
|
|
g_assert(!strcmp(qstring_get_str(qstr), id));
|
|
|
|
obj = qdict_get(data, "addr");
|
|
|
|
v = qobject_input_visitor_new(obj);
|
|
visit_type_SocketAddress(v, NULL, addr, NULL);
|
|
visit_free(v);
|
|
qobject_unref(resp);
|
|
}
|
|
|
|
static void wait_stream_disconnected(QTestState *qts, const char *id)
|
|
{
|
|
QDict *resp, *data;
|
|
QString *qstr;
|
|
|
|
resp = qtest_qmp_eventwait_ref(qts, "NETDEV_STREAM_DISCONNECTED");
|
|
g_assert_nonnull(resp);
|
|
data = qdict_get_qdict(resp, "data");
|
|
g_assert_nonnull(data);
|
|
|
|
qstr = qobject_to(QString, qdict_get(data, "netdev-id"));
|
|
g_assert_nonnull(data);
|
|
|
|
g_assert(!strcmp(qstring_get_str(qstr), id));
|
|
qobject_unref(resp);
|
|
}
|
|
|
|
static void test_stream_unix_reconnect(void)
|
|
{
|
|
QTestState *qts0, *qts1;
|
|
SocketAddress *addr;
|
|
gchar *path;
|
|
|
|
path = g_strconcat(tmpdir, "/stream_unix_reconnect", NULL);
|
|
qts0 = qtest_initf("-nodefaults -M none "
|
|
"-netdev stream,id=st0,server=true,addr.type=unix,"
|
|
"addr.path=%s", path);
|
|
|
|
EXPECT_STATE(qts0, "st0: index=0,type=stream,listening\r\n", 0);
|
|
|
|
qts1 = qtest_initf("-nodefaults -M none "
|
|
"-netdev stream,server=false,id=st0,addr.type=unix,"
|
|
"addr.path=%s,reconnect=1", path);
|
|
|
|
wait_stream_connected(qts0, "st0", &addr);
|
|
g_assert_cmpint(addr->type, ==, SOCKET_ADDRESS_TYPE_UNIX);
|
|
g_assert_cmpstr(addr->u.q_unix.path, ==, path);
|
|
qapi_free_SocketAddress(addr);
|
|
|
|
/* kill server */
|
|
qtest_quit(qts0);
|
|
|
|
/* check client has been disconnected */
|
|
wait_stream_disconnected(qts1, "st0");
|
|
|
|
/* restart server */
|
|
qts0 = qtest_initf("-nodefaults -M none "
|
|
"-netdev stream,id=st0,server=true,addr.type=unix,"
|
|
"addr.path=%s", path);
|
|
|
|
/* wait connection events*/
|
|
wait_stream_connected(qts0, "st0", &addr);
|
|
g_assert_cmpint(addr->type, ==, SOCKET_ADDRESS_TYPE_UNIX);
|
|
g_assert_cmpstr(addr->u.q_unix.path, ==, path);
|
|
qapi_free_SocketAddress(addr);
|
|
|
|
wait_stream_connected(qts1, "st0", &addr);
|
|
g_assert_cmpint(addr->type, ==, SOCKET_ADDRESS_TYPE_UNIX);
|
|
g_assert_cmpstr(addr->u.q_unix.path, ==, path);
|
|
qapi_free_SocketAddress(addr);
|
|
|
|
qtest_quit(qts1);
|
|
qtest_quit(qts0);
|
|
g_free(path);
|
|
}
|
|
|
|
static void test_stream_inet_ipv6(void)
|
|
{
|
|
QTestState *qts0, *qts1;
|
|
char *expect;
|
|
int port;
|
|
|
|
port = inet_get_free_port(true);
|
|
qts0 = qtest_initf("-nodefaults -M none "
|
|
"-netdev stream,id=st0,server=true,addr.type=inet,"
|
|
"addr.ipv4=off,addr.ipv6=on,"
|
|
"addr.host=::1,addr.port=%d", port);
|
|
|
|
EXPECT_STATE(qts0, "st0: index=0,type=stream,listening\r\n", 0);
|
|
|
|
qts1 = qtest_initf("-nodefaults -M none "
|
|
"-netdev stream,server=false,id=st0,addr.type=inet,"
|
|
"addr.ipv4=off,addr.ipv6=on,"
|
|
"addr.host=::1,addr.port=%d", port);
|
|
|
|
expect = g_strdup_printf("st0: index=0,type=stream,tcp:::1:%d\r\n",
|
|
port);
|
|
EXPECT_STATE(qts1, expect, 0);
|
|
g_free(expect);
|
|
|
|
/* the port is unknown, check only the address */
|
|
EXPECT_STATE(qts0, "st0: index=0,type=stream,tcp:::1", ':');
|
|
|
|
qtest_quit(qts1);
|
|
qtest_quit(qts0);
|
|
}
|
|
|
|
static void test_stream_unix(void)
|
|
{
|
|
QTestState *qts0, *qts1;
|
|
char *expect;
|
|
gchar *path;
|
|
|
|
path = g_strconcat(tmpdir, "/stream_unix", NULL);
|
|
|
|
qts0 = qtest_initf("-nodefaults -M none "
|
|
"-netdev stream,id=st0,server=true,"
|
|
"addr.type=unix,addr.path=%s,",
|
|
path);
|
|
|
|
EXPECT_STATE(qts0, "st0: index=0,type=stream,listening\r\n", 0);
|
|
|
|
qts1 = qtest_initf("-nodefaults -M none "
|
|
"-netdev stream,id=st0,server=false,"
|
|
"addr.type=unix,addr.path=%s",
|
|
path);
|
|
|
|
expect = g_strdup_printf("st0: index=0,type=stream,unix:%s\r\n", path);
|
|
EXPECT_STATE(qts1, expect, 0);
|
|
EXPECT_STATE(qts0, expect, 0);
|
|
g_free(expect);
|
|
g_free(path);
|
|
|
|
qtest_quit(qts1);
|
|
qtest_quit(qts0);
|
|
}
|
|
|
|
#ifdef CONFIG_LINUX
|
|
static void test_stream_unix_abstract(void)
|
|
{
|
|
QTestState *qts0, *qts1;
|
|
char *expect;
|
|
gchar *path;
|
|
|
|
path = g_strconcat(tmpdir, "/stream_unix_abstract", NULL);
|
|
|
|
qts0 = qtest_initf("-nodefaults -M none "
|
|
"-netdev stream,id=st0,server=true,"
|
|
"addr.type=unix,addr.path=%s,"
|
|
"addr.abstract=on",
|
|
path);
|
|
|
|
EXPECT_STATE(qts0, "st0: index=0,type=stream,listening\r\n", 0);
|
|
|
|
qts1 = qtest_initf("-nodefaults -M none "
|
|
"-netdev stream,id=st0,server=false,"
|
|
"addr.type=unix,addr.path=%s,addr.abstract=on",
|
|
path);
|
|
|
|
expect = g_strdup_printf("st0: index=0,type=stream,unix:%s\r\n", path);
|
|
EXPECT_STATE(qts1, expect, 0);
|
|
EXPECT_STATE(qts0, expect, 0);
|
|
g_free(expect);
|
|
g_free(path);
|
|
|
|
qtest_quit(qts1);
|
|
qtest_quit(qts0);
|
|
}
|
|
#endif
|
|
|
|
#ifndef _WIN32
|
|
static void test_stream_fd(void)
|
|
{
|
|
QTestState *qts0, *qts1;
|
|
int sock[2];
|
|
int ret;
|
|
|
|
ret = socketpair(AF_LOCAL, SOCK_STREAM, 0, sock);
|
|
g_assert_true(ret == 0);
|
|
|
|
qts0 = qtest_initf("-nodefaults -M none "
|
|
"-netdev stream,id=st0,addr.type=fd,addr.str=%d",
|
|
sock[0]);
|
|
|
|
EXPECT_STATE(qts0, "st0: index=0,type=stream,unix:\r\n", 0);
|
|
|
|
qts1 = qtest_initf("-nodefaults -M none "
|
|
"-netdev stream,id=st0,addr.type=fd,addr.str=%d",
|
|
sock[1]);
|
|
|
|
EXPECT_STATE(qts1, "st0: index=0,type=stream,unix:\r\n", 0);
|
|
EXPECT_STATE(qts0, "st0: index=0,type=stream,unix:\r\n", 0);
|
|
|
|
qtest_quit(qts1);
|
|
qtest_quit(qts0);
|
|
|
|
close(sock[0]);
|
|
close(sock[1]);
|
|
}
|
|
#endif
|
|
|
|
static void test_dgram_inet(void)
|
|
{
|
|
QTestState *qts0, *qts1;
|
|
char *expect;
|
|
int port[2];
|
|
int nb;
|
|
|
|
nb = inet_get_free_port_multiple(2, port, false);
|
|
g_assert_cmpint(nb, ==, 2);
|
|
|
|
qts0 = qtest_initf("-nodefaults -M none "
|
|
"-netdev dgram,id=st0,"
|
|
"local.type=inet,local.host=127.0.0.1,local.port=%d,"
|
|
"remote.type=inet,remote.host=127.0.0.1,remote.port=%d",
|
|
port[0], port[1]);
|
|
|
|
expect = g_strdup_printf("st0: index=0,type=dgram,"
|
|
"udp=127.0.0.1:%d/127.0.0.1:%d\r\n",
|
|
port[0], port[1]);
|
|
EXPECT_STATE(qts0, expect, 0);
|
|
g_free(expect);
|
|
|
|
qts1 = qtest_initf("-nodefaults -M none "
|
|
"-netdev dgram,id=st0,"
|
|
"local.type=inet,local.host=127.0.0.1,local.port=%d,"
|
|
"remote.type=inet,remote.host=127.0.0.1,remote.port=%d",
|
|
port[1], port[0]);
|
|
|
|
expect = g_strdup_printf("st0: index=0,type=dgram,"
|
|
"udp=127.0.0.1:%d/127.0.0.1:%d\r\n",
|
|
port[1], port[0]);
|
|
EXPECT_STATE(qts1, expect, 0);
|
|
g_free(expect);
|
|
|
|
qtest_quit(qts1);
|
|
qtest_quit(qts0);
|
|
}
|
|
|
|
#if !defined(_WIN32) && !defined(CONFIG_DARWIN)
|
|
static void test_dgram_mcast(void)
|
|
{
|
|
QTestState *qts;
|
|
|
|
qts = qtest_initf("-nodefaults -M none "
|
|
"-netdev dgram,id=st0,"
|
|
"remote.type=inet,remote.host=230.0.0.1,remote.port=1234");
|
|
|
|
EXPECT_STATE(qts, "st0: index=0,type=dgram,mcast=230.0.0.1:1234\r\n", 0);
|
|
|
|
qtest_quit(qts);
|
|
}
|
|
#endif
|
|
|
|
#ifndef _WIN32
|
|
static void test_dgram_unix(void)
|
|
{
|
|
QTestState *qts0, *qts1;
|
|
char *expect;
|
|
gchar *path0, *path1;
|
|
|
|
path0 = g_strconcat(tmpdir, "/dgram_unix0", NULL);
|
|
path1 = g_strconcat(tmpdir, "/dgram_unix1", NULL);
|
|
|
|
qts0 = qtest_initf("-nodefaults -M none "
|
|
"-netdev dgram,id=st0,local.type=unix,local.path=%s,"
|
|
"remote.type=unix,remote.path=%s",
|
|
path0, path1);
|
|
|
|
expect = g_strdup_printf("st0: index=0,type=dgram,udp=%s:%s\r\n",
|
|
path0, path1);
|
|
EXPECT_STATE(qts0, expect, 0);
|
|
g_free(expect);
|
|
|
|
qts1 = qtest_initf("-nodefaults -M none "
|
|
"-netdev dgram,id=st0,local.type=unix,local.path=%s,"
|
|
"remote.type=unix,remote.path=%s",
|
|
path1, path0);
|
|
|
|
|
|
expect = g_strdup_printf("st0: index=0,type=dgram,udp=%s:%s\r\n",
|
|
path1, path0);
|
|
EXPECT_STATE(qts1, expect, 0);
|
|
g_free(expect);
|
|
|
|
unlink(path0);
|
|
g_free(path0);
|
|
unlink(path1);
|
|
g_free(path1);
|
|
|
|
qtest_quit(qts1);
|
|
qtest_quit(qts0);
|
|
}
|
|
|
|
static void test_dgram_fd(void)
|
|
{
|
|
QTestState *qts0, *qts1;
|
|
char *expect;
|
|
int ret;
|
|
int sv[2];
|
|
|
|
ret = socketpair(PF_UNIX, SOCK_DGRAM, 0, sv);
|
|
g_assert_cmpint(ret, !=, -1);
|
|
|
|
qts0 = qtest_initf("-nodefaults -M none "
|
|
"-netdev dgram,id=st0,local.type=fd,local.str=%d",
|
|
sv[0]);
|
|
|
|
expect = g_strdup_printf("st0: index=0,type=dgram,fd=%d unix\r\n", sv[0]);
|
|
EXPECT_STATE(qts0, expect, 0);
|
|
g_free(expect);
|
|
|
|
qts1 = qtest_initf("-nodefaults -M none "
|
|
"-netdev dgram,id=st0,local.type=fd,local.str=%d",
|
|
sv[1]);
|
|
|
|
|
|
expect = g_strdup_printf("st0: index=0,type=dgram,fd=%d unix\r\n", sv[1]);
|
|
EXPECT_STATE(qts1, expect, 0);
|
|
g_free(expect);
|
|
|
|
qtest_quit(qts1);
|
|
qtest_quit(qts0);
|
|
|
|
close(sv[0]);
|
|
close(sv[1]);
|
|
}
|
|
#endif
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int ret;
|
|
bool has_ipv4, has_ipv6, has_afunix;
|
|
g_autoptr(GError) err = NULL;
|
|
|
|
socket_init();
|
|
g_test_init(&argc, &argv, NULL);
|
|
|
|
if (socket_check_protocol_support(&has_ipv4, &has_ipv6) < 0) {
|
|
g_error("socket_check_protocol_support() failed\n");
|
|
}
|
|
|
|
tmpdir = g_dir_make_tmp("netdev-socket.XXXXXX", &err);
|
|
if (tmpdir == NULL) {
|
|
g_error("Can't create temporary directory in %s: %s",
|
|
g_get_tmp_dir(), err->message);
|
|
}
|
|
|
|
if (has_ipv4) {
|
|
qtest_add_func("/netdev/stream/inet/ipv4", test_stream_inet_ipv4);
|
|
qtest_add_func("/netdev/dgram/inet", test_dgram_inet);
|
|
#if !defined(_WIN32) && !defined(CONFIG_DARWIN)
|
|
qtest_add_func("/netdev/dgram/mcast", test_dgram_mcast);
|
|
#endif
|
|
}
|
|
if (has_ipv6) {
|
|
qtest_add_func("/netdev/stream/inet/ipv6", test_stream_inet_ipv6);
|
|
}
|
|
|
|
socket_check_afunix_support(&has_afunix);
|
|
if (has_afunix) {
|
|
#ifndef _WIN32
|
|
qtest_add_func("/netdev/dgram/unix", test_dgram_unix);
|
|
#endif
|
|
qtest_add_func("/netdev/stream/unix", test_stream_unix);
|
|
qtest_add_func("/netdev/stream/unix/reconnect",
|
|
test_stream_unix_reconnect);
|
|
#ifdef CONFIG_LINUX
|
|
qtest_add_func("/netdev/stream/unix/abstract",
|
|
test_stream_unix_abstract);
|
|
#endif
|
|
#ifndef _WIN32
|
|
qtest_add_func("/netdev/stream/fd", test_stream_fd);
|
|
qtest_add_func("/netdev/dgram/fd", test_dgram_fd);
|
|
#endif
|
|
}
|
|
|
|
ret = g_test_run();
|
|
|
|
g_rmdir(tmpdir);
|
|
g_free(tmpdir);
|
|
|
|
return ret;
|
|
}
|