| /* SPDX-License-Identifier: GPL-2.0-or-later */ | |
| /* | |
| * QTest testcase for filter-buffer | |
| * | |
| * Copyright (c) 2025 Red Hat, Inc. | |
| * Author: Jason Wang <jasowang@redhat.com> | |
| */ | |
| #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(); | |
| } |