diff --git a/net/filter-buffer.c b/net/filter-buffer.c index a36be31dc8..427da24097 100644 --- a/net/filter-buffer.c +++ b/net/filter-buffer.c @@ -159,6 +159,7 @@ static void filter_buffer_set_interval(Object *obj, Visitor *v, Error **errp) { FilterBufferState *s = FILTER_BUFFER(obj); + NetFilterState *nf = NETFILTER(obj); uint32_t value; if (!visit_type_uint32(v, name, &value, errp)) { @@ -170,6 +171,11 @@ static void filter_buffer_set_interval(Object *obj, Visitor *v, return; } s->interval = value; + + if (nf->netdev && nf->on) { + timer_mod(&s->release_timer, + qemu_clock_get_us(QEMU_CLOCK_VIRTUAL) + s->interval); + } } static void filter_buffer_class_init(ObjectClass *oc, const void *data) diff --git a/net/filter-mirror.c b/net/filter-mirror.c index d2bfde42e8..6ac28067a2 100644 --- a/net/filter-mirror.c +++ b/net/filter-mirror.c @@ -179,9 +179,16 @@ static void redirector_chr_event(void *opaque, QEMUChrEvent event) MirrorState *s = FILTER_REDIRECTOR(nf); switch (event) { + case CHR_EVENT_OPENED: + if (nf->on) { + qemu_chr_fe_set_handlers_full(&s->chr_in, redirector_chr_can_read, + redirector_chr_read, redirector_chr_event, + NULL, nf, NULL, false, false); + } + break; case CHR_EVENT_CLOSED: - qemu_chr_fe_set_handlers(&s->chr_in, NULL, NULL, NULL, - NULL, NULL, NULL, true); + qemu_chr_fe_set_handlers_full(&s->chr_in, NULL, NULL, redirector_chr_event, + NULL, nf, NULL, false, false); break; default: break; @@ -306,9 +313,11 @@ static void filter_redirector_setup(NetFilterState *nf, Error **errp) return; } - qemu_chr_fe_set_handlers(&s->chr_in, redirector_chr_can_read, - redirector_chr_read, redirector_chr_event, - NULL, nf, NULL, true); + if (nf->on) { + qemu_chr_fe_set_handlers(&s->chr_in, redirector_chr_can_read, + redirector_chr_read, redirector_chr_event, + NULL, nf, NULL, true); + } } if (s->outdev) { @@ -324,6 +333,24 @@ static void filter_redirector_setup(NetFilterState *nf, Error **errp) } } +static void filter_redirector_status_changed(NetFilterState *nf, Error **errp) +{ + MirrorState *s = FILTER_REDIRECTOR(nf); + + if (!s->indev) { + return; + } + + if (nf->on) { + qemu_chr_fe_set_handlers(&s->chr_in, redirector_chr_can_read, + redirector_chr_read, redirector_chr_event, + NULL, nf, NULL, true); + } else { + qemu_chr_fe_set_handlers(&s->chr_in, NULL, NULL, NULL, + NULL, NULL, NULL, true); + } +} + static char *filter_redirector_get_indev(Object *obj, Error **errp) { MirrorState *s = FILTER_REDIRECTOR(obj); @@ -440,6 +467,7 @@ static void filter_redirector_class_init(ObjectClass *oc, const void *data) nfc->setup = filter_redirector_setup; nfc->cleanup = filter_redirector_cleanup; nfc->receive_iov = filter_redirector_receive_iov; + nfc->status_changed = filter_redirector_status_changed; } static void filter_mirror_init(Object *obj) diff --git a/net/tap-bsd.c b/net/tap-bsd.c index 3fd300d46f..c39daf9385 100644 --- a/net/tap-bsd.c +++ b/net/tap-bsd.c @@ -206,8 +206,9 @@ error: } #endif /* __FreeBSD__ */ -void tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp) +bool tap_set_sndbuf(int fd, int sndbuf, Error **errp) { + return true; } int tap_probe_vnet_hdr(int fd, Error **errp) diff --git a/net/tap-linux.c b/net/tap-linux.c index 909c4f1fcf..3cd7d26710 100644 --- a/net/tap-linux.c +++ b/net/tap-linux.c @@ -143,23 +143,14 @@ int tap_open(char *ifname, int ifname_size, int *vnet_hdr, * Ethernet NICs generally have txqueuelen=1000, so 1Mb is * a good value, given a 1500 byte MTU. */ -#define TAP_DEFAULT_SNDBUF 0 - -void tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp) +bool tap_set_sndbuf(int fd, int sndbuf, Error **errp) { - int sndbuf; - - sndbuf = !tap->has_sndbuf ? TAP_DEFAULT_SNDBUF : - tap->sndbuf > INT_MAX ? INT_MAX : - tap->sndbuf; - - if (!sndbuf) { - sndbuf = INT_MAX; - } - - if (ioctl(fd, TUNSETSNDBUF, &sndbuf) == -1 && tap->has_sndbuf) { + if (ioctl(fd, TUNSETSNDBUF, &sndbuf) == -1) { error_setg_errno(errp, errno, "TUNSETSNDBUF ioctl failed"); + return false; } + + return true; } int tap_probe_vnet_hdr(int fd, Error **errp) @@ -214,10 +205,11 @@ bool tap_probe_has_tunnel(int fd) void tap_fd_set_vnet_hdr_len(int fd, int len) { - if (ioctl(fd, TUNSETVNETHDRSZ, &len) == -1) { - fprintf(stderr, "TUNSETVNETHDRSZ ioctl() failed: %s. Exiting.\n", - strerror(errno)); - abort(); + int ret; + + ret = ioctl(fd, TUNSETVNETHDRSZ, &len); + if (ret != 0) { + error_report("TUNSETVNETHDRSZ ioctl() failed: %s.", strerror(errno)); } } diff --git a/net/tap-solaris.c b/net/tap-solaris.c index faf7922ea8..8704b1084b 100644 --- a/net/tap-solaris.c +++ b/net/tap-solaris.c @@ -208,8 +208,9 @@ int tap_open(char *ifname, int ifname_size, int *vnet_hdr, return fd; } -void tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp) +bool tap_set_sndbuf(int fd, int sndbuf, Error **errp) { + return true; } int tap_probe_vnet_hdr(int fd, Error **errp) diff --git a/net/tap-stub.c b/net/tap-stub.c index f7a5e0c163..6aa60d96ad 100644 --- a/net/tap-stub.c +++ b/net/tap-stub.c @@ -33,8 +33,9 @@ int tap_open(char *ifname, int ifname_size, int *vnet_hdr, return -1; } -void tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp) +bool tap_set_sndbuf(int fd, int sndbuf, Error **errp) { + return true; } int tap_probe_vnet_hdr(int fd, Error **errp) diff --git a/net/tap.c b/net/tap.c index bfba3fd7a7..8d7ab6ba6f 100644 --- a/net/tap.c +++ b/net/tap.c @@ -91,6 +91,21 @@ static void launch_script(const char *setup_script, const char *ifname, static void tap_send(void *opaque); static void tap_writable(void *opaque); +static char *tap_parse_script(const char *script_arg, const char *default_path) +{ + g_autofree char *res = g_strdup(script_arg); + + if (!res) { + res = get_relocated_path(default_path); + } + + if (res[0] == '\0' || strcmp(res, "no") == 0) { + return NULL; + } + + return g_steal_pointer(&res); +} + static void tap_update_fd_handler(TAPState *s) { qemu_set_fd_handler(s->fd, @@ -311,11 +326,9 @@ static void tap_exit_notify(Notifier *notifier, void *data) TAPState *s = container_of(notifier, TAPState, exit); Error *err = NULL; - if (s->down_script[0]) { - launch_script(s->down_script, s->down_script_arg, s->fd, &err); - if (err) { - error_report_err(err); - } + launch_script(s->down_script, s->down_script_arg, s->fd, &err); + if (err) { + error_report_err(err); } } @@ -331,8 +344,11 @@ static void tap_cleanup(NetClientState *nc) qemu_purge_queued_packets(nc); - tap_exit_notify(&s->exit, NULL); - qemu_remove_exit_notifier(&s->exit); + if (s->exit.notify) { + tap_exit_notify(&s->exit, NULL); + qemu_remove_exit_notifier(&s->exit); + s->exit.notify = NULL; + } tap_read_poll(s, false); tap_write_poll(s, false); @@ -428,9 +444,6 @@ static TAPState *net_tap_fd_init(NetClientState *peer, tap_read_poll(s, true); s->vhost_net = NULL; - s->exit.notify = tap_exit_notify; - qemu_add_exit_notifier(&s->exit); - return s; } @@ -676,9 +689,7 @@ static int net_tap_init(const NetdevTapOptions *tap, int *vnet_hdr, return -1; } - if (setup_script && - setup_script[0] != '\0' && - strcmp(setup_script, "no") != 0) { + if (setup_script) { launch_script(setup_script, ifname, fd, &err); if (err) { error_propagate(errp, err); @@ -698,13 +709,14 @@ static void net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer, const char *downscript, const char *vhostfdname, int vnet_hdr, int fd, Error **errp) { - Error *err = NULL; TAPState *s = net_tap_fd_init(peer, model, name, fd, vnet_hdr); int vhostfd; + bool sndbuf_required = tap->has_sndbuf; + int sndbuf = + (tap->has_sndbuf && tap->sndbuf) ? MIN(tap->sndbuf, INT_MAX) : INT_MAX; - tap_set_sndbuf(s->fd, tap, &err); - if (err) { - error_propagate(errp, err); + if (!tap_set_sndbuf(fd, sndbuf, sndbuf_required ? errp : NULL) && + sndbuf_required) { goto failed; } @@ -714,12 +726,14 @@ static void net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer, qemu_set_info_str(&s->nc, "helper=%s", tap->helper); } else { qemu_set_info_str(&s->nc, "ifname=%s,script=%s,downscript=%s", ifname, - script, downscript); + script ?: "no", downscript ?: "no"); - if (strcmp(downscript, "no") != 0) { + if (downscript) { snprintf(s->down_script, sizeof(s->down_script), "%s", downscript); snprintf(s->down_script_arg, sizeof(s->down_script_arg), "%s", ifname); + s->exit.notify = tap_exit_notify; + qemu_add_exit_notifier(&s->exit); } } @@ -736,9 +750,8 @@ static void net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer, } if (vhostfdname) { - vhostfd = monitor_fd_param(monitor_cur(), vhostfdname, &err); + vhostfd = monitor_fd_param(monitor_cur(), vhostfdname, errp); if (vhostfd == -1) { - error_propagate(errp, err); goto failed; } if (!qemu_set_blocking(vhostfd, false, errp)) { @@ -768,9 +781,6 @@ static void net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer, "vhost-net requested but could not be initialized"); goto failed; } - } else if (vhostfdname) { - error_setg(errp, "vhostfd(s)= is not valid without vhost"); - goto failed; } return; @@ -811,8 +821,6 @@ int net_init_tap(const Netdev *netdev, const char *name, const NetdevTapOptions *tap; int fd, vnet_hdr = 0, i = 0, queues; /* for the no-fd, no-helper case */ - const char *script; - const char *downscript; Error *err = NULL; const char *vhostfdname; char ifname[128]; @@ -822,8 +830,6 @@ int net_init_tap(const Netdev *netdev, const char *name, tap = &netdev->u.tap; queues = tap->has_queues ? tap->queues : 1; vhostfdname = tap->vhostfd; - script = tap->script; - downscript = tap->downscript; /* QEMU hubs do not support multiqueue tap, in this case peer is set. * For -netdev, peer is always NULL. */ @@ -832,6 +838,11 @@ int net_init_tap(const Netdev *netdev, const char *name, return -1; } + if (tap->has_vhost && !tap->vhost && (tap->vhostfds || tap->vhostfd)) { + error_setg(errp, "vhostfd(s)= is not valid without vhost"); + return -1; + } + if (tap->fd) { if (tap->ifname || tap->script || tap->downscript || tap->has_vnet_hdr || tap->helper || tap->has_queues || @@ -859,7 +870,7 @@ int net_init_tap(const Netdev *netdev, const char *name, } net_init_tap_one(tap, peer, "tap", name, NULL, - script, downscript, + NULL, NULL, vhostfdname, vnet_hdr, fd, &err); if (err) { error_propagate(errp, err); @@ -920,7 +931,7 @@ int net_init_tap(const Netdev *netdev, const char *name, } net_init_tap_one(tap, peer, "tap", name, ifname, - script, downscript, + NULL, NULL, tap->vhostfds ? vhost_fds[i] : NULL, vnet_hdr, fd, &err); if (err) { @@ -965,7 +976,7 @@ free_fail: } net_init_tap_one(tap, peer, "bridge", name, ifname, - script, downscript, vhostfdname, + NULL, NULL, vhostfdname, vnet_hdr, fd, &err); if (err) { error_propagate(errp, err); @@ -973,21 +984,16 @@ free_fail: return -1; } } else { - g_autofree char *default_script = NULL; - g_autofree char *default_downscript = NULL; + g_autofree char *script = + tap_parse_script(tap->script, DEFAULT_NETWORK_SCRIPT); + g_autofree char *downscript = + tap_parse_script(tap->downscript, DEFAULT_NETWORK_DOWN_SCRIPT); + if (tap->vhostfds) { error_setg(errp, "vhostfds= is invalid if fds= wasn't specified"); return -1; } - if (!script) { - script = default_script = get_relocated_path(DEFAULT_NETWORK_SCRIPT); - } - if (!downscript) { - downscript = default_downscript = - get_relocated_path(DEFAULT_NETWORK_DOWN_SCRIPT); - } - if (tap->ifname) { pstrcpy(ifname, sizeof ifname, tap->ifname); } else { @@ -995,7 +1001,7 @@ free_fail: } for (i = 0; i < queues; i++) { - fd = net_tap_init(tap, &vnet_hdr, i >= 1 ? "no" : script, + fd = net_tap_init(tap, &vnet_hdr, i >= 1 ? NULL : script, ifname, sizeof ifname, queues > 1, errp); if (fd == -1) { return -1; @@ -1010,8 +1016,8 @@ free_fail: } net_init_tap_one(tap, peer, "tap", name, ifname, - i >= 1 ? "no" : script, - i >= 1 ? "no" : downscript, + i >= 1 ? NULL : script, + i >= 1 ? NULL : downscript, vhostfdname, vnet_hdr, fd, &err); if (err) { error_propagate(errp, err); diff --git a/net/tap_int.h b/net/tap_int.h index b76a05044b..dc4f484006 100644 --- a/net/tap_int.h +++ b/net/tap_int.h @@ -26,7 +26,6 @@ #ifndef NET_TAP_INT_H #define NET_TAP_INT_H -#include "qapi/qapi-types-net.h" #include "net/net.h" int tap_open(char *ifname, int ifname_size, int *vnet_hdr, @@ -34,7 +33,7 @@ int tap_open(char *ifname, int ifname_size, int *vnet_hdr, ssize_t tap_read_packet(int tapfd, uint8_t *buf, int maxlen); -void tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp); +bool tap_set_sndbuf(int fd, int sndbuf, Error **errp); int tap_probe_vnet_hdr(int fd, Error **errp); int tap_probe_has_ufo(int fd); int tap_probe_has_uso(int fd); diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index a8b09d065f..dfb83650c6 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -46,6 +46,7 @@ qtests_cxl = \ # for the availability of the default NICs in the tests qtests_filter = \ (get_option('default_devices') and slirp.found() ? ['test-netfilter'] : []) + \ + (get_option('default_devices') and host_os != 'windows' ? ['test-filter-buffer'] : []) + \ (get_option('default_devices') and host_os != 'windows' ? ['test-filter-mirror'] : []) + \ (get_option('default_devices') and host_os != 'windows' ? ['test-filter-redirector'] : []) diff --git a/tests/qtest/test-filter-buffer.c b/tests/qtest/test-filter-buffer.c new file mode 100644 index 0000000000..441cbb975c --- /dev/null +++ b/tests/qtest/test-filter-buffer.c @@ -0,0 +1,169 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * QTest testcase for filter-buffer + * + * Copyright (c) 2025 Red Hat, Inc. + * Author: Jason Wang + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qobject/qdict.h" +#include "qemu/iov.h" +#include "qemu/sockets.h" + +/* + * Test that changing interval at runtime affects packet release timing. + * + * Traffic flow with filter-buffer and filter-redirector: + * + * test side | qemu side + * | + * +--------+ | +---------+ + * | send +------------------------>| backend | + * | sock[0]| | +----+----+ + * +--------+ | | + * | +----v----+ + * | | fbuf0 | filter-buffer (queue=tx) + * | +----+----+ + * | | + * | +----v----+ +----------+ + * | | rd0 +->| chardev0 | + * | +---------+ +----+-----+ + * | | + * +--------+ | | + * | recv |<--------------------------------------+ + * | sock | | + * +--------+ | + * + * The test verifies that when interval is changed via qom-set, the timer + * is rescheduled immediately, causing buffered packets to be released + * at the new interval rather than waiting for the old interval to elapse. + */ +static void test_change_interval_timer(void) +{ + QTestState *qts; + QDict *response; + int backend_sock[2], recv_sock; + int ret; + char send_buf[] = "Hello filter-buffer!"; + char recv_buf[128]; + char sock_path[] = "filter-buffer-test.XXXXXX"; + uint32_t size = sizeof(send_buf); + uint32_t len; + + size = htonl(size); + + ret = socketpair(PF_UNIX, SOCK_STREAM, 0, backend_sock); + g_assert_cmpint(ret, !=, -1); + + ret = mkstemp(sock_path); + g_assert_cmpint(ret, !=, -1); + + /* + * Start QEMU with: + * - socket backend connected to our socketpair + * - filter-buffer with a very long interval (1000 seconds) + * - filter-redirector to send released packets to a chardev socket + * + * queue=tx intercepts packets going from backend to the guest, + * i.e., data we send from the test side. + */ + qts = qtest_initf( + "-nic socket,id=qtest-bn0,fd=%d " + "-chardev socket,id=chardev0,path=%s,server=on,wait=off " + "-object filter-buffer,id=fbuf0,netdev=qtest-bn0," + "queue=tx,interval=1000000000 " + "-object filter-redirector,id=rd0,netdev=qtest-bn0," + "queue=tx,outdev=chardev0", + backend_sock[1], sock_path); + + /* Connect to the chardev socket to receive redirected packets */ + recv_sock = unix_connect(sock_path, NULL); + g_assert_cmpint(recv_sock, !=, -1); + + /* Send a QMP command to ensure chardev connection is established */ + qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}"); + + /* + * Send a packet from the test side. + * It should be buffered by filter-buffer. + */ + struct iovec iov[] = { + { + .iov_base = &size, + .iov_len = sizeof(size), + }, { + .iov_base = send_buf, + .iov_len = sizeof(send_buf), + }, + }; + + ret = iov_send(backend_sock[0], iov, 2, 0, sizeof(size) + sizeof(send_buf)); + g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size)); + + /* + * Advance virtual clock by 1 second (1,000,000,000 ns). + * This is much less than the 1000 second interval, so the packet + * should still be buffered. + */ + qtest_clock_step(qts, 1000000000LL); + + /* Try to receive with non-blocking - should fail (packet still buffered) */ + ret = recv(recv_sock, recv_buf, sizeof(recv_buf), MSG_DONTWAIT); + g_assert_cmpint(ret, ==, -1); + g_assert(errno == EAGAIN || errno == EWOULDBLOCK); + + /* + * Now change the interval to 1000 us (1ms) via qom-set. + * This should reschedule the timer to fire in 1ms from now. + */ + response = qtest_qmp(qts, + "{'execute': 'qom-set'," + " 'arguments': {" + " 'path': 'fbuf0'," + " 'property': 'interval'," + " 'value': 1000" + "}}"); + g_assert(response); + g_assert(!qdict_haskey(response, "error")); + qobject_unref(response); + + /* + * Advance virtual clock by 2ms (2,000,000 ns). + * This exceeds the new 1ms interval, so the timer should fire + * and release the buffered packet. + * + * If the interval change didn't take effect immediately, we would + * still be waiting for the original 1000 second interval to elapse, + * and the packet would not be released. + */ + qtest_clock_step(qts, 2000000LL); + + /* + * Now we should be able to receive the packet through the redirector. + * The packet was released by filter-buffer and sent to filter-redirector, + * which forwarded it to the chardev socket. + */ + ret = recv(recv_sock, &len, sizeof(len), 0); + g_assert_cmpint(ret, ==, sizeof(len)); + len = ntohl(len); + g_assert_cmpint(len, ==, sizeof(send_buf)); + + ret = recv(recv_sock, recv_buf, len, 0); + g_assert_cmpint(ret, ==, len); + g_assert_cmpstr(recv_buf, ==, send_buf); + + close(recv_sock); + close(backend_sock[0]); + unlink(sock_path); + qtest_quit(qts); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + qtest_add_func("/netfilter/change_interval_timer", + test_change_interval_timer); + return g_test_run(); +} diff --git a/tests/qtest/test-filter-redirector.c b/tests/qtest/test-filter-redirector.c index a996a80c1c..5540c232c0 100644 --- a/tests/qtest/test-filter-redirector.c +++ b/tests/qtest/test-filter-redirector.c @@ -196,10 +196,298 @@ static void test_redirector_rx(void) qtest_quit(qts); } +/* + * Test filter-redirector status on/off switching. + * + * This test verifies that: + * 1. When status is set to "off", the filter stops receiving data from indev + * 2. When status is set back to "on", the filter resumes receiving data + */ +static void test_redirector_status(void) +{ + int backend_sock[2], send_sock; + uint32_t ret = 0, len = 0; + char send_buf[] = "Hello!!"; + char sock_path0[] = "filter-redirector0.XXXXXX"; + char *recv_buf; + uint32_t size = sizeof(send_buf); + size = htonl(size); + QTestState *qts; + struct timeval tv; + fd_set rfds; + + ret = socketpair(PF_UNIX, SOCK_STREAM, 0, backend_sock); + g_assert_cmpint(ret, !=, -1); + + ret = mkstemp(sock_path0); + g_assert_cmpint(ret, !=, -1); + + /* + * Setup a simple rx path: + * chardev (sock_path0) -> filter-redirector -> socket backend + */ + qts = qtest_initf( + "-nic socket,id=qtest-bn0,fd=%d " + "-chardev socket,id=redirector0,path=%s,server=on,wait=off " + "-object filter-redirector,id=qtest-f0,netdev=qtest-bn0," + "queue=rx,indev=redirector0 ", + backend_sock[1], sock_path0); + + send_sock = unix_connect(sock_path0, NULL); + g_assert_cmpint(send_sock, !=, -1); + + /* send a qmp command to guarantee that 'connected' is setting to true. */ + qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}"); + + struct iovec iov[] = { + { + .iov_base = &size, + .iov_len = sizeof(size), + }, { + .iov_base = send_buf, + .iov_len = sizeof(send_buf), + }, + }; + + /* + * Test 1: Set status to "off" and verify data is not received + */ + qtest_qmp_assert_success(qts, + "{ 'execute': 'qom-set', 'arguments': " + "{ 'path': '/objects/qtest-f0', 'property': 'status', 'value': 'off' }}"); + + ret = iov_send(send_sock, iov, 2, 0, sizeof(size) + sizeof(send_buf)); + g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size)); + + /* + * Use select with timeout to check if data arrives. + * When status is off, no data should arrive. + */ + FD_ZERO(&rfds); + FD_SET(backend_sock[0], &rfds); + tv.tv_sec = 0; + tv.tv_usec = 500000; /* 500ms timeout */ + ret = select(backend_sock[0] + 1, &rfds, NULL, NULL, &tv); + g_assert_cmpint(ret, ==, 0); /* Should timeout, no data */ + + /* + * Test 2: Set status back to "on" and verify data is received + */ + qtest_qmp_assert_success(qts, + "{ 'execute': 'qom-set', 'arguments': " + "{ 'path': '/objects/qtest-f0', 'property': 'status', 'value': 'on' }}"); + + ret = iov_send(send_sock, iov, 2, 0, sizeof(size) + sizeof(send_buf)); + g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size)); + + ret = recv(backend_sock[0], &len, sizeof(len), 0); + g_assert_cmpint(ret, ==, sizeof(len)); + len = ntohl(len); + + g_assert_cmpint(len, ==, sizeof(send_buf)); + recv_buf = g_malloc(len); + ret = recv(backend_sock[0], recv_buf, len, 0); + g_assert_cmpint(ret, ==, len); + g_assert_cmpstr(recv_buf, ==, send_buf); + + g_free(recv_buf); + close(send_sock); + unlink(sock_path0); + qtest_quit(qts); +} + +/* + * Test filter-redirector created with status=off. + * + * This test verifies that when a filter-redirector is created with + * status=off, it does not receive data until status is set to on. + */ +static void test_redirector_init_status_off(void) +{ + int backend_sock[2], send_sock; + uint32_t ret = 0, len = 0; + char send_buf[] = "Hello!!"; + char sock_path0[] = "filter-redirector0.XXXXXX"; + char *recv_buf; + uint32_t size = sizeof(send_buf); + size = htonl(size); + QTestState *qts; + struct timeval tv; + fd_set rfds; + + ret = socketpair(PF_UNIX, SOCK_STREAM, 0, backend_sock); + g_assert_cmpint(ret, !=, -1); + + ret = mkstemp(sock_path0); + g_assert_cmpint(ret, !=, -1); + + /* + * Create filter-redirector with status=off from the start + */ + qts = qtest_initf( + "-nic socket,id=qtest-bn0,fd=%d " + "-chardev socket,id=redirector0,path=%s,server=on,wait=off " + "-object filter-redirector,id=qtest-f0,netdev=qtest-bn0," + "queue=rx,indev=redirector0,status=off ", + backend_sock[1], sock_path0); + + send_sock = unix_connect(sock_path0, NULL); + g_assert_cmpint(send_sock, !=, -1); + + qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}"); + + struct iovec iov[] = { + { + .iov_base = &size, + .iov_len = sizeof(size), + }, { + .iov_base = send_buf, + .iov_len = sizeof(send_buf), + }, + }; + + /* + * Test 1: Filter was created with status=off, data should not be received + */ + ret = iov_send(send_sock, iov, 2, 0, sizeof(size) + sizeof(send_buf)); + g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size)); + + FD_ZERO(&rfds); + FD_SET(backend_sock[0], &rfds); + tv.tv_sec = 0; + tv.tv_usec = 500000; + ret = select(backend_sock[0] + 1, &rfds, NULL, NULL, &tv); + g_assert_cmpint(ret, ==, 0); /* Should timeout, no data */ + + /* + * Test 2: Set status to "on" and verify data is received + */ + qtest_qmp_assert_success(qts, + "{ 'execute': 'qom-set', 'arguments': " + "{ 'path': '/objects/qtest-f0', 'property': 'status', 'value': 'on' }}"); + + ret = iov_send(send_sock, iov, 2, 0, sizeof(size) + sizeof(send_buf)); + g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size)); + + ret = recv(backend_sock[0], &len, sizeof(len), 0); + g_assert_cmpint(ret, ==, sizeof(len)); + len = ntohl(len); + + g_assert_cmpint(len, ==, sizeof(send_buf)); + recv_buf = g_malloc(len); + ret = recv(backend_sock[0], recv_buf, len, 0); + g_assert_cmpint(ret, ==, len); + g_assert_cmpstr(recv_buf, ==, send_buf); + + g_free(recv_buf); + close(send_sock); + unlink(sock_path0); + qtest_quit(qts); +} + +static void test_redirector_rx_event_opened(void) +{ + int backend_sock[2], send_sock; + uint32_t ret = 0, len = 0; + char send_buf[] = "Hello!!"; + char send_buf2[] = "Hello2!!"; + char sock_path0[] = "filter-redirector0.XXXXXX"; + char *recv_buf; + uint32_t size = sizeof(send_buf); + uint32_t size2 = sizeof(send_buf2); + size = htonl(size); + size2 = htonl(size2); + QTestState *qts; + + ret = socketpair(PF_UNIX, SOCK_STREAM, 0, backend_sock); + g_assert_cmpint(ret, !=, -1); + + ret = mkstemp(sock_path0); + g_assert_cmpint(ret, !=, -1); + + qts = qtest_initf( + "-nic socket,id=qtest-bn0,fd=%d " + "-chardev socket,id=redirector0,path=%s,server=on,wait=off " + "-object filter-redirector,id=qtest-f0,netdev=qtest-bn0," + "queue=rx,indev=redirector0 ", + backend_sock[1], sock_path0); + + struct iovec iov[] = { + { + .iov_base = &size, + .iov_len = sizeof(size), + }, { + .iov_base = send_buf, + .iov_len = sizeof(send_buf), + }, + }; + + struct iovec iov2[] = { + { + .iov_base = &size2, + .iov_len = sizeof(size2), + }, { + .iov_base = send_buf2, + .iov_len = sizeof(send_buf2), + }, + }; + + /* First connection */ + send_sock = unix_connect(sock_path0, NULL); + g_assert_cmpint(send_sock, !=, -1); + qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}"); + + ret = iov_send(send_sock, iov, 2, 0, sizeof(size) + sizeof(send_buf)); + g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size)); + + ret = recv(backend_sock[0], &len, sizeof(len), 0); + g_assert_cmpint(ret, ==, sizeof(len)); + len = ntohl(len); + g_assert_cmpint(len, ==, sizeof(send_buf)); + recv_buf = g_malloc(len); + ret = recv(backend_sock[0], recv_buf, len, 0); + g_assert_cmpint(ret, ==, len); + g_assert_cmpstr(recv_buf, ==, send_buf); + g_free(recv_buf); + + close(send_sock); + + /* Verify disconnection handling if needed, but mainly we want to test Reconnection */ + qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}"); + + /* Second connection */ + send_sock = unix_connect(sock_path0, NULL); + g_assert_cmpint(send_sock, !=, -1); + qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}"); + + ret = iov_send(send_sock, iov2, 2, 0, sizeof(size2) + sizeof(send_buf2)); + g_assert_cmpint(ret, ==, sizeof(send_buf2) + sizeof(size2)); + + ret = recv(backend_sock[0], &len, sizeof(len), 0); + g_assert_cmpint(ret, ==, sizeof(len)); + len = ntohl(len); + g_assert_cmpint(len, ==, sizeof(send_buf2)); + recv_buf = g_malloc(len); + ret = recv(backend_sock[0], recv_buf, len, 0); + g_assert_cmpint(ret, ==, len); + g_assert_cmpstr(recv_buf, ==, send_buf2); + g_free(recv_buf); + + close(send_sock); + unlink(sock_path0); + qtest_quit(qts); + close(backend_sock[0]); +} + int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); qtest_add_func("/netfilter/redirector_tx", test_redirector_tx); qtest_add_func("/netfilter/redirector_rx", test_redirector_rx); + qtest_add_func("/netfilter/redirector_status", test_redirector_status); + qtest_add_func("/netfilter/redirector_init_status_off", + test_redirector_init_status_off); + qtest_add_func("/netfilter/redirector_rx_event_opened", + test_redirector_rx_event_opened); return g_test_run(); }