|  | /* | 
|  | * 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 "qobject/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-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: 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/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; | 
|  | } |