xref: /openbmc/qemu/tests/qtest/netdev-socket.c (revision f51d3fb1)
1 /*
2  * QTest testcase for netdev stream and dgram
3  *
4  * Copyright (c) 2022 Red Hat, Inc.
5  *
6  * SPDX-License-Identifier: GPL-2.0-or-later
7  */
8 
9 #include "qemu/osdep.h"
10 #include "qemu/sockets.h"
11 #include <glib/gstdio.h>
12 #include "../unit/socket-helpers.h"
13 #include "libqtest.h"
14 #include "qapi/qmp/qstring.h"
15 #include "qemu/sockets.h"
16 #include "qapi/qobject-input-visitor.h"
17 #include "qapi/qapi-visit-sockets.h"
18 
19 #define CONNECTION_TIMEOUT    60
20 
21 #define EXPECT_STATE(q, e, t)                             \
22 do {                                                      \
23     char *resp = NULL;                                    \
24     g_test_timer_start();                                 \
25     do {                                                  \
26         g_free(resp);                                     \
27         resp = qtest_hmp(q, "info network");              \
28         if (t) {                                          \
29             strrchr(resp, t)[0] = 0;                      \
30         }                                                 \
31         if (g_str_equal(resp, e)) {                       \
32             break;                                        \
33         }                                                 \
34     } while (g_test_timer_elapsed() < CONNECTION_TIMEOUT); \
35     g_assert_cmpstr(resp, ==, e);                         \
36     g_free(resp);                                         \
37 } while (0)
38 
39 static gchar *tmpdir;
40 
inet_get_free_port_socket_ipv4(int sock)41 static int inet_get_free_port_socket_ipv4(int sock)
42 {
43     struct sockaddr_in addr;
44     socklen_t len;
45 
46     memset(&addr, 0, sizeof(addr));
47     addr.sin_family = AF_INET;
48     addr.sin_addr.s_addr = INADDR_ANY;
49     addr.sin_port = 0;
50     if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
51         return -1;
52     }
53 
54     len = sizeof(addr);
55     if (getsockname(sock,  (struct sockaddr *)&addr, &len) < 0) {
56         return -1;
57     }
58 
59     return ntohs(addr.sin_port);
60 }
61 
inet_get_free_port_socket_ipv6(int sock)62 static int inet_get_free_port_socket_ipv6(int sock)
63 {
64     struct sockaddr_in6 addr;
65     socklen_t len;
66 
67     memset(&addr, 0, sizeof(addr));
68     addr.sin6_family = AF_INET6;
69     addr.sin6_addr = in6addr_any;
70     addr.sin6_port = 0;
71     if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
72         return -1;
73     }
74 
75     len = sizeof(addr);
76     if (getsockname(sock,  (struct sockaddr *)&addr, &len) < 0) {
77         return -1;
78     }
79 
80     return ntohs(addr.sin6_port);
81 }
82 
inet_get_free_port_multiple(int nb,int * port,bool ipv6)83 static int inet_get_free_port_multiple(int nb, int *port, bool ipv6)
84 {
85     g_autofree int *sock = g_new(int, nb);
86     int i;
87 
88     for (i = 0; i < nb; i++) {
89         sock[i] = socket(ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0);
90         if (sock[i] < 0) {
91             break;
92         }
93         port[i] = ipv6 ? inet_get_free_port_socket_ipv6(sock[i]) :
94                          inet_get_free_port_socket_ipv4(sock[i]);
95         if (port[i] == -1) {
96             break;
97         }
98     }
99 
100     nb = i;
101     for (i = 0; i < nb; i++) {
102         close(sock[i]);
103     }
104 
105     return nb;
106 }
107 
inet_get_free_port(bool ipv6)108 static int inet_get_free_port(bool ipv6)
109 {
110     int nb, port;
111 
112     nb = inet_get_free_port_multiple(1, &port, ipv6);
113     g_assert_cmpint(nb, ==, 1);
114 
115     return port;
116 }
117 
test_stream_inet_ipv4(void)118 static void test_stream_inet_ipv4(void)
119 {
120     QTestState *qts0, *qts1;
121     char *expect;
122     int port;
123 
124     port = inet_get_free_port(false);
125     qts0 = qtest_initf("-nodefaults -M none "
126                        "-netdev stream,id=st0,server=true,addr.type=inet,"
127                        "addr.ipv4=on,addr.ipv6=off,"
128                        "addr.host=127.0.0.1,addr.port=%d", port);
129 
130     EXPECT_STATE(qts0, "st0: index=0,type=stream,listening\r\n", 0);
131 
132     qts1 = qtest_initf("-nodefaults -M none "
133                        "-netdev stream,server=false,id=st0,addr.type=inet,"
134                        "addr.ipv4=on,addr.ipv6=off,"
135                        "addr.host=127.0.0.1,addr.port=%d", port);
136 
137     expect = g_strdup_printf("st0: index=0,type=stream,tcp:127.0.0.1:%d\r\n",
138                              port);
139     EXPECT_STATE(qts1, expect, 0);
140     g_free(expect);
141 
142     /* the port is unknown, check only the address */
143     EXPECT_STATE(qts0, "st0: index=0,type=stream,tcp:127.0.0.1", ':');
144 
145     qtest_quit(qts1);
146     qtest_quit(qts0);
147 }
148 
wait_stream_connected(QTestState * qts,const char * id,SocketAddress ** addr)149 static void wait_stream_connected(QTestState *qts, const char *id,
150                                   SocketAddress **addr)
151 {
152     QDict *resp, *data;
153     QString *qstr;
154     QObject *obj;
155     Visitor *v = NULL;
156 
157     resp = qtest_qmp_eventwait_ref(qts, "NETDEV_STREAM_CONNECTED");
158     g_assert_nonnull(resp);
159     data = qdict_get_qdict(resp, "data");
160     g_assert_nonnull(data);
161 
162     qstr = qobject_to(QString, qdict_get(data, "netdev-id"));
163     g_assert_nonnull(data);
164 
165     g_assert(!strcmp(qstring_get_str(qstr), id));
166 
167     obj = qdict_get(data, "addr");
168 
169     v = qobject_input_visitor_new(obj);
170     visit_type_SocketAddress(v, NULL, addr, NULL);
171     visit_free(v);
172     qobject_unref(resp);
173 }
174 
wait_stream_disconnected(QTestState * qts,const char * id)175 static void wait_stream_disconnected(QTestState *qts, const char *id)
176 {
177     QDict *resp, *data;
178     QString *qstr;
179 
180     resp = qtest_qmp_eventwait_ref(qts, "NETDEV_STREAM_DISCONNECTED");
181     g_assert_nonnull(resp);
182     data = qdict_get_qdict(resp, "data");
183     g_assert_nonnull(data);
184 
185     qstr = qobject_to(QString, qdict_get(data, "netdev-id"));
186     g_assert_nonnull(data);
187 
188     g_assert(!strcmp(qstring_get_str(qstr), id));
189     qobject_unref(resp);
190 }
191 
test_stream_unix_reconnect(void)192 static void test_stream_unix_reconnect(void)
193 {
194     QTestState *qts0, *qts1;
195     SocketAddress *addr;
196     gchar *path;
197 
198     path = g_strconcat(tmpdir, "/stream_unix_reconnect", NULL);
199     qts0 = qtest_initf("-nodefaults -M none "
200                        "-netdev stream,id=st0,server=true,addr.type=unix,"
201                        "addr.path=%s", path);
202 
203     EXPECT_STATE(qts0, "st0: index=0,type=stream,listening\r\n", 0);
204 
205     qts1 = qtest_initf("-nodefaults -M none "
206                        "-netdev stream,server=false,id=st0,addr.type=unix,"
207                        "addr.path=%s,reconnect=1", path);
208 
209     wait_stream_connected(qts0, "st0", &addr);
210     g_assert_cmpint(addr->type, ==, SOCKET_ADDRESS_TYPE_UNIX);
211     g_assert_cmpstr(addr->u.q_unix.path, ==, path);
212     qapi_free_SocketAddress(addr);
213 
214     /* kill server */
215     qtest_quit(qts0);
216 
217     /* check client has been disconnected */
218     wait_stream_disconnected(qts1, "st0");
219 
220     /* restart server */
221     qts0 = qtest_initf("-nodefaults -M none "
222                        "-netdev stream,id=st0,server=true,addr.type=unix,"
223                        "addr.path=%s", path);
224 
225     /* wait connection events*/
226     wait_stream_connected(qts0, "st0", &addr);
227     g_assert_cmpint(addr->type, ==, SOCKET_ADDRESS_TYPE_UNIX);
228     g_assert_cmpstr(addr->u.q_unix.path, ==, path);
229     qapi_free_SocketAddress(addr);
230 
231     wait_stream_connected(qts1, "st0", &addr);
232     g_assert_cmpint(addr->type, ==, SOCKET_ADDRESS_TYPE_UNIX);
233     g_assert_cmpstr(addr->u.q_unix.path, ==, path);
234     qapi_free_SocketAddress(addr);
235 
236     qtest_quit(qts1);
237     qtest_quit(qts0);
238     g_free(path);
239 }
240 
test_stream_inet_ipv6(void)241 static void test_stream_inet_ipv6(void)
242 {
243     QTestState *qts0, *qts1;
244     char *expect;
245     int port;
246 
247     port = inet_get_free_port(true);
248     qts0 = qtest_initf("-nodefaults -M none "
249                        "-netdev stream,id=st0,server=true,addr.type=inet,"
250                        "addr.ipv4=off,addr.ipv6=on,"
251                        "addr.host=::1,addr.port=%d", port);
252 
253     EXPECT_STATE(qts0, "st0: index=0,type=stream,listening\r\n", 0);
254 
255     qts1 = qtest_initf("-nodefaults -M none "
256                        "-netdev stream,server=false,id=st0,addr.type=inet,"
257                        "addr.ipv4=off,addr.ipv6=on,"
258                        "addr.host=::1,addr.port=%d", port);
259 
260     expect = g_strdup_printf("st0: index=0,type=stream,tcp:::1:%d\r\n",
261                              port);
262     EXPECT_STATE(qts1, expect, 0);
263     g_free(expect);
264 
265     /* the port is unknown, check only the address */
266     EXPECT_STATE(qts0, "st0: index=0,type=stream,tcp:::1", ':');
267 
268     qtest_quit(qts1);
269     qtest_quit(qts0);
270 }
271 
test_stream_unix(void)272 static void test_stream_unix(void)
273 {
274     QTestState *qts0, *qts1;
275     char *expect;
276     gchar *path;
277 
278     path = g_strconcat(tmpdir, "/stream_unix", NULL);
279 
280     qts0 = qtest_initf("-nodefaults -M none "
281                        "-netdev stream,id=st0,server=true,"
282                        "addr.type=unix,addr.path=%s,",
283                        path);
284 
285     EXPECT_STATE(qts0, "st0: index=0,type=stream,listening\r\n", 0);
286 
287     qts1 = qtest_initf("-nodefaults -M none "
288                        "-netdev stream,id=st0,server=false,"
289                        "addr.type=unix,addr.path=%s",
290                        path);
291 
292     expect = g_strdup_printf("st0: index=0,type=stream,unix:%s\r\n", path);
293     EXPECT_STATE(qts1, expect, 0);
294     EXPECT_STATE(qts0, expect, 0);
295     g_free(expect);
296     g_free(path);
297 
298     qtest_quit(qts1);
299     qtest_quit(qts0);
300 }
301 
302 #ifdef CONFIG_LINUX
test_stream_unix_abstract(void)303 static void test_stream_unix_abstract(void)
304 {
305     QTestState *qts0, *qts1;
306     char *expect;
307     gchar *path;
308 
309     path = g_strconcat(tmpdir, "/stream_unix_abstract", NULL);
310 
311     qts0 = qtest_initf("-nodefaults -M none "
312                        "-netdev stream,id=st0,server=true,"
313                        "addr.type=unix,addr.path=%s,"
314                        "addr.abstract=on",
315                        path);
316 
317     EXPECT_STATE(qts0, "st0: index=0,type=stream,listening\r\n", 0);
318 
319     qts1 = qtest_initf("-nodefaults -M none "
320                        "-netdev stream,id=st0,server=false,"
321                        "addr.type=unix,addr.path=%s,addr.abstract=on",
322                        path);
323 
324     expect = g_strdup_printf("st0: index=0,type=stream,unix:%s\r\n", path);
325     EXPECT_STATE(qts1, expect, 0);
326     EXPECT_STATE(qts0, expect, 0);
327     g_free(expect);
328     g_free(path);
329 
330     qtest_quit(qts1);
331     qtest_quit(qts0);
332 }
333 #endif
334 
335 #ifndef _WIN32
test_stream_fd(void)336 static void test_stream_fd(void)
337 {
338     QTestState *qts0, *qts1;
339     int sock[2];
340     int ret;
341 
342     ret = socketpair(AF_LOCAL, SOCK_STREAM, 0, sock);
343     g_assert_true(ret == 0);
344 
345     qts0 = qtest_initf("-nodefaults -M none "
346                        "-netdev stream,id=st0,addr.type=fd,addr.str=%d",
347                        sock[0]);
348 
349     EXPECT_STATE(qts0, "st0: index=0,type=stream,unix:\r\n", 0);
350 
351     qts1 = qtest_initf("-nodefaults -M none "
352                        "-netdev stream,id=st0,addr.type=fd,addr.str=%d",
353                        sock[1]);
354 
355     EXPECT_STATE(qts1, "st0: index=0,type=stream,unix:\r\n", 0);
356     EXPECT_STATE(qts0, "st0: index=0,type=stream,unix:\r\n", 0);
357 
358     qtest_quit(qts1);
359     qtest_quit(qts0);
360 
361     close(sock[0]);
362     close(sock[1]);
363 }
364 #endif
365 
test_dgram_inet(void)366 static void test_dgram_inet(void)
367 {
368     QTestState *qts0, *qts1;
369     char *expect;
370     int port[2];
371     int nb;
372 
373     nb = inet_get_free_port_multiple(2, port, false);
374     g_assert_cmpint(nb, ==, 2);
375 
376     qts0 = qtest_initf("-nodefaults -M none "
377                        "-netdev dgram,id=st0,"
378                        "local.type=inet,local.host=127.0.0.1,local.port=%d,"
379                        "remote.type=inet,remote.host=127.0.0.1,remote.port=%d",
380                         port[0], port[1]);
381 
382     expect = g_strdup_printf("st0: index=0,type=dgram,"
383                              "udp=127.0.0.1:%d/127.0.0.1:%d\r\n",
384                              port[0], port[1]);
385     EXPECT_STATE(qts0, expect, 0);
386     g_free(expect);
387 
388     qts1 = qtest_initf("-nodefaults -M none "
389                        "-netdev dgram,id=st0,"
390                        "local.type=inet,local.host=127.0.0.1,local.port=%d,"
391                        "remote.type=inet,remote.host=127.0.0.1,remote.port=%d",
392                         port[1], port[0]);
393 
394     expect = g_strdup_printf("st0: index=0,type=dgram,"
395                              "udp=127.0.0.1:%d/127.0.0.1:%d\r\n",
396                              port[1], port[0]);
397     EXPECT_STATE(qts1, expect, 0);
398     g_free(expect);
399 
400     qtest_quit(qts1);
401     qtest_quit(qts0);
402 }
403 
404 #if !defined(_WIN32) && !defined(CONFIG_DARWIN)
test_dgram_mcast(void)405 static void test_dgram_mcast(void)
406 {
407     QTestState *qts;
408 
409     qts = qtest_initf("-nodefaults -M none "
410                       "-netdev dgram,id=st0,"
411                       "remote.type=inet,remote.host=230.0.0.1,remote.port=1234");
412 
413     EXPECT_STATE(qts, "st0: index=0,type=dgram,mcast=230.0.0.1:1234\r\n", 0);
414 
415     qtest_quit(qts);
416 }
417 #endif
418 
419 #ifndef _WIN32
test_dgram_unix(void)420 static void test_dgram_unix(void)
421 {
422     QTestState *qts0, *qts1;
423     char *expect;
424     gchar *path0, *path1;
425 
426     path0 = g_strconcat(tmpdir, "/dgram_unix0", NULL);
427     path1 = g_strconcat(tmpdir, "/dgram_unix1", NULL);
428 
429     qts0 = qtest_initf("-nodefaults -M none "
430                        "-netdev dgram,id=st0,local.type=unix,local.path=%s,"
431                        "remote.type=unix,remote.path=%s",
432                        path0, path1);
433 
434     expect = g_strdup_printf("st0: index=0,type=dgram,udp=%s:%s\r\n",
435                              path0, path1);
436     EXPECT_STATE(qts0, expect, 0);
437     g_free(expect);
438 
439     qts1 = qtest_initf("-nodefaults -M none "
440                        "-netdev dgram,id=st0,local.type=unix,local.path=%s,"
441                        "remote.type=unix,remote.path=%s",
442                        path1, path0);
443 
444 
445     expect = g_strdup_printf("st0: index=0,type=dgram,udp=%s:%s\r\n",
446                              path1, path0);
447     EXPECT_STATE(qts1, expect, 0);
448     g_free(expect);
449 
450     unlink(path0);
451     g_free(path0);
452     unlink(path1);
453     g_free(path1);
454 
455     qtest_quit(qts1);
456     qtest_quit(qts0);
457 }
458 
test_dgram_fd(void)459 static void test_dgram_fd(void)
460 {
461     QTestState *qts0, *qts1;
462     char *expect;
463     int ret;
464     int sv[2];
465 
466     ret = socketpair(PF_UNIX, SOCK_DGRAM, 0, sv);
467     g_assert_cmpint(ret, !=, -1);
468 
469     qts0 = qtest_initf("-nodefaults -M none "
470                        "-netdev dgram,id=st0,local.type=fd,local.str=%d",
471                        sv[0]);
472 
473     expect = g_strdup_printf("st0: index=0,type=dgram,fd=%d unix\r\n", sv[0]);
474     EXPECT_STATE(qts0, expect, 0);
475     g_free(expect);
476 
477     qts1 = qtest_initf("-nodefaults -M none "
478                        "-netdev dgram,id=st0,local.type=fd,local.str=%d",
479                        sv[1]);
480 
481 
482     expect = g_strdup_printf("st0: index=0,type=dgram,fd=%d unix\r\n", sv[1]);
483     EXPECT_STATE(qts1, expect, 0);
484     g_free(expect);
485 
486     qtest_quit(qts1);
487     qtest_quit(qts0);
488 
489     close(sv[0]);
490     close(sv[1]);
491 }
492 #endif
493 
main(int argc,char ** argv)494 int main(int argc, char **argv)
495 {
496     int ret;
497     bool has_ipv4, has_ipv6, has_afunix;
498     g_autoptr(GError) err = NULL;
499 
500     socket_init();
501     g_test_init(&argc, &argv, NULL);
502 
503     if (socket_check_protocol_support(&has_ipv4, &has_ipv6) < 0) {
504         g_error("socket_check_protocol_support() failed\n");
505     }
506 
507     tmpdir = g_dir_make_tmp("netdev-socket.XXXXXX", &err);
508     if (tmpdir == NULL) {
509         g_error("Can't create temporary directory in %s: %s",
510                 g_get_tmp_dir(), err->message);
511     }
512 
513     if (has_ipv4) {
514         qtest_add_func("/netdev/stream/inet/ipv4", test_stream_inet_ipv4);
515         qtest_add_func("/netdev/dgram/inet", test_dgram_inet);
516 #if !defined(_WIN32) && !defined(CONFIG_DARWIN)
517         qtest_add_func("/netdev/dgram/mcast", test_dgram_mcast);
518 #endif
519     }
520     if (has_ipv6) {
521         qtest_add_func("/netdev/stream/inet/ipv6", test_stream_inet_ipv6);
522     }
523 
524     socket_check_afunix_support(&has_afunix);
525     if (has_afunix) {
526 #ifndef _WIN32
527         qtest_add_func("/netdev/dgram/unix", test_dgram_unix);
528 #endif
529         qtest_add_func("/netdev/stream/unix/oneshot", test_stream_unix);
530         qtest_add_func("/netdev/stream/unix/reconnect",
531                        test_stream_unix_reconnect);
532 #ifdef CONFIG_LINUX
533         qtest_add_func("/netdev/stream/unix/abstract",
534                        test_stream_unix_abstract);
535 #endif
536 #ifndef _WIN32
537         qtest_add_func("/netdev/stream/fd", test_stream_fd);
538         qtest_add_func("/netdev/dgram/fd", test_dgram_fd);
539 #endif
540     }
541 
542     ret = g_test_run();
543 
544     g_rmdir(tmpdir);
545     g_free(tmpdir);
546 
547     return ret;
548 }
549