Jason Wang <[email protected]> writes:

> 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"
>  

Some extra changes here^

>  /* add a netfilter to a netdev and then remove it */
>  static void add_one_netfilter(void)

Reply via email to