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.

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


Reply via email to