qemu/tests/qtest/netdev-socket.c
Daniel P. Berrangé cc91ca64d8 net: add explicit info about connecting/listening state
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>
2024-01-11 11:39:26 +01:00

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;
}