On Thu, Dec 25, 2025 at 3:24 PM Jason Wang <[email protected]> wrote: > > Add test_change_interval_timer to verify that modifying the 'interval' > property of filter-buffer at runtime takes effect immediately. > > The test uses socket backend and filter-redirector to verify timer behavior: > - Creates filter-buffer with a very long interval (1000 seconds) > - Sends a packet which gets buffered > - Advances virtual clock by 1 second, verifies packet is still buffered > - Changes interval to 1ms via qom-set (timer should be rescheduled) > - Advances virtual clock by 2ms, verifies packet is now released > - This proves the timer was rescheduled immediately when interval changed > > The test uses filter-redirector to observe when packets are released > by filter-buffer, providing end-to-end verification of the timer > rescheduling behavior.
If user try to simulate network latency by filter-buffer, the accuracy of time is important. Do we need add some note about the first buffered packet time not equel to dynamic changed time (default interval time - new qmp cmd effected time + changed time ?). Another issue is what level of stable accuracy the QEMU filter-buffer can be achieved in actual testing? Maybe need to tell user recommended test range? Thanks Chen > > Signed-off-by: Jason Wang <[email protected]> > --- > tests/qtest/meson.build | 1 + > tests/qtest/test-filter-buffer.c | 169 +++++++++++++++++++++++++++++++ > tests/qtest/test-netfilter.c | 3 + > 3 files changed, 173 insertions(+) > create mode 100644 tests/qtest/test-filter-buffer.c > > diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build > index 669d07c06b..ffa85ba984 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 <[email protected]> > + */ > + > +#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-netfilter.c b/tests/qtest/test-netfilter.c > index 326d4bd85f..b7271055d6 100644 > --- a/tests/qtest/test-netfilter.c > +++ b/tests/qtest/test-netfilter.c > @@ -10,7 +10,10 @@ > > #include "qemu/osdep.h" > #include "libqtest-single.h" > +#include "libqtest.h" > #include "qobject/qdict.h" > +#include "qemu/iov.h" > +#include "qemu/sockets.h" > > /* add a netfilter to a netdev and then remove it */ > static void add_one_netfilter(void) > -- > 2.34.1 >
