| /* | |
| * 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 <glib/gstdio.h> | |
| #include "../unit/socket-helpers.h" | |
| #include "libqtest.h" | |
| #include "qapi/qapi-visit-sockets.h" | |
| #include "qapi/qobject-input-visitor.h" | |
| #include "qemu/sockets.h" | |
| #include "qobject/qdict.h" | |
| #include "qobject/qlist.h" | |
| #include "qobject/qstring.h" | |
| #define CONNECTION_TIMEOUT 60 | |
| /* | |
| * Only checks info-str; type is implicit from the netdev created by each test | |
| */ | |
| static char *query_net_info_str(QTestState *q, const char *name) | |
| { | |
| QDict *ret; | |
| QList *clients; | |
| QListEntry *entry; | |
| ret = qtest_qmp_assert_success_ref(q, "{'execute': 'x-query-network'}"); | |
| clients = qdict_get_qlist(ret, "clients"); | |
| g_assert_nonnull(clients); | |
| QLIST_FOREACH_ENTRY(clients, entry) { | |
| QDict *client = qobject_to(QDict, qlist_entry_obj(entry)); | |
| if (g_str_equal(qdict_get_str(client, "name"), name)) { | |
| char *info = g_strdup(qdict_get_str(client, "info-str")); | |
| qobject_unref(ret); | |
| return info; | |
| } | |
| } | |
| g_assert_not_reached(); | |
| } | |
| #define EXPECT_STATE(q, id, e, t) \ | |
| do { \ | |
| char *info = NULL; \ | |
| g_test_timer_start(); \ | |
| do { \ | |
| g_free(info); \ | |
| info = query_net_info_str(q, id); \ | |
| if (t) { \ | |
| char *p = strrchr(info, t); \ | |
| if (p) { \ | |
| p[0] = 0; \ | |
| } \ | |
| } \ | |
| if (g_str_equal(info, e)) { \ | |
| break; \ | |
| } \ | |
| } while (g_test_timer_elapsed() < CONNECTION_TIMEOUT); \ | |
| g_assert_cmpstr(info, ==, e); \ | |
| g_free(info); \ | |
| } 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", "listening", 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("tcp:127.0.0.1:%d", port); | |
| EXPECT_STATE(qts1, "st0", expect, 0); | |
| g_free(expect); | |
| /* the port is unknown, check only the address */ | |
| EXPECT_STATE(qts0, "st0", "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", "listening", 0); | |
| qts1 = qtest_initf("-nodefaults -M none " | |
| "-netdev stream,server=false,id=st0,addr.type=unix," | |
| "addr.path=%s,reconnect-ms=1000", 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", "listening", 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("tcp:::1:%d", port); | |
| EXPECT_STATE(qts1, "st0", expect, 0); | |
| g_free(expect); | |
| /* the port is unknown, check only the address */ | |
| EXPECT_STATE(qts0, "st0", "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", "listening", 0); | |
| qts1 = qtest_initf("-nodefaults -M none " | |
| "-netdev stream,id=st0,server=false," | |
| "addr.type=unix,addr.path=%s", | |
| path); | |
| expect = g_strdup_printf("unix:%s", path); | |
| EXPECT_STATE(qts1, "st0", expect, 0); | |
| EXPECT_STATE(qts0, "st0", 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", "listening", 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("unix:%s", path); | |
| EXPECT_STATE(qts1, "st0", expect, 0); | |
| EXPECT_STATE(qts0, "st0", 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", "unix:", 0); | |
| qts1 = qtest_initf("-nodefaults -M none " | |
| "-netdev stream,id=st0,addr.type=fd,addr.str=%d", | |
| sock[1]); | |
| EXPECT_STATE(qts1, "st0", "unix:", 0); | |
| EXPECT_STATE(qts0, "st0", "unix:", 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("udp=127.0.0.1:%d/127.0.0.1:%d", | |
| port[0], port[1]); | |
| EXPECT_STATE(qts0, "st0", 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("udp=127.0.0.1:%d/127.0.0.1:%d", | |
| port[1], port[0]); | |
| EXPECT_STATE(qts1, "st0", 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", "mcast=230.0.0.1:1234", 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("udp=%s:%s", path0, path1); | |
| EXPECT_STATE(qts0, "st0", 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("udp=%s:%s", path1, path0); | |
| EXPECT_STATE(qts1, "st0", 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("fd=%d unix", sv[0]); | |
| EXPECT_STATE(qts0, "st0", 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("fd=%d unix", sv[1]); | |
| EXPECT_STATE(qts1, "st0", 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/oneshot", 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; | |
| } |