| 1 | /* SPDX-License-Identifier: GPL-2.0-or-later */ |
| 2 | /* |
| 3 | * QTest testcase for filter-buffer |
| 4 | * |
| 5 | * Copyright (c) 2025 Red Hat, Inc. |
| 6 | * Author: Jason Wang <jasowang@redhat.com> |
| 7 | */ |
| 8 | |
| 9 | #include "qemu/osdep.h" |
| 10 | #include "libqtest.h" |
| 11 | #include "qobject/qdict.h" |
| 12 | #include "qemu/iov.h" |
| 13 | #include "qemu/sockets.h" |
| 14 | |
| 15 | /* |
| 16 | * Test that changing interval at runtime affects packet release timing. |
| 17 | * |
| 18 | * Traffic flow with filter-buffer and filter-redirector: |
| 19 | * |
| 20 | * test side | qemu side |
| 21 | * | |
| 22 | * +--------+ | +---------+ |
| 23 | * | send +------------------------>| backend | |
| 24 | * | sock[0]| | +----+----+ |
| 25 | * +--------+ | | |
| 26 | * | +----v----+ |
| 27 | * | | fbuf0 | filter-buffer (queue=tx) |
| 28 | * | +----+----+ |
| 29 | * | | |
| 30 | * | +----v----+ +----------+ |
| 31 | * | | rd0 +->| chardev0 | |
| 32 | * | +---------+ +----+-----+ |
| 33 | * | | |
| 34 | * +--------+ | | |
| 35 | * | recv |<--------------------------------------+ |
| 36 | * | sock | | |
| 37 | * +--------+ | |
| 38 | * |
| 39 | * The test verifies that when interval is changed via qom-set, the timer |
| 40 | * is rescheduled immediately, causing buffered packets to be released |
| 41 | * at the new interval rather than waiting for the old interval to elapse. |
| 42 | */ |
| 43 | static void test_change_interval_timer(void) |
| 44 | { |
| 45 | QTestState *qts; |
| 46 | QDict *response; |
| 47 | int backend_sock[2], recv_sock; |
| 48 | int ret; |
| 49 | char send_buf[] = "Hello filter-buffer!"; |
| 50 | char recv_buf[128]; |
| 51 | char sock_path[] = "filter-buffer-test.XXXXXX"; |
| 52 | uint32_t size = sizeof(send_buf); |
| 53 | uint32_t len; |
| 54 | |
| 55 | size = htonl(size); |
| 56 | |
| 57 | ret = socketpair(PF_UNIX, SOCK_STREAM, 0, backend_sock); |
| 58 | g_assert_cmpint(ret, !=, -1); |
| 59 | |
| 60 | ret = mkstemp(sock_path); |
| 61 | g_assert_cmpint(ret, !=, -1); |
| 62 | |
| 63 | /* |
| 64 | * Start QEMU with: |
| 65 | * - socket backend connected to our socketpair |
| 66 | * - filter-buffer with a very long interval (1000 seconds) |
| 67 | * - filter-redirector to send released packets to a chardev socket |
| 68 | * |
| 69 | * queue=tx intercepts packets going from backend to the guest, |
| 70 | * i.e., data we send from the test side. |
| 71 | */ |
| 72 | qts = qtest_initf( |
| 73 | "-nic socket,id=qtest-bn0,fd=%d " |
| 74 | "-chardev socket,id=chardev0,path=%s,server=on,wait=off " |
| 75 | "-object filter-buffer,id=fbuf0,netdev=qtest-bn0," |
| 76 | "queue=tx,interval=1000000000 " |
| 77 | "-object filter-redirector,id=rd0,netdev=qtest-bn0," |
| 78 | "queue=tx,outdev=chardev0", |
| 79 | backend_sock[1], sock_path); |
| 80 | |
| 81 | /* Connect to the chardev socket to receive redirected packets */ |
| 82 | recv_sock = unix_connect(sock_path, NULL); |
| 83 | g_assert_cmpint(recv_sock, !=, -1); |
| 84 | |
| 85 | /* Send a QMP command to ensure chardev connection is established */ |
| 86 | qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}"); |
| 87 | |
| 88 | /* |
| 89 | * Send a packet from the test side. |
| 90 | * It should be buffered by filter-buffer. |
| 91 | */ |
| 92 | struct iovec iov[] = { |
| 93 | { |
| 94 | .iov_base = &size, |
| 95 | .iov_len = sizeof(size), |
| 96 | }, { |
| 97 | .iov_base = send_buf, |
| 98 | .iov_len = sizeof(send_buf), |
| 99 | }, |
| 100 | }; |
| 101 | |
| 102 | ret = iov_send(backend_sock[0], iov, 2, 0, sizeof(size) + sizeof(send_buf)); |
| 103 | g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size)); |
| 104 | |
| 105 | /* |
| 106 | * Advance virtual clock by 1 second (1,000,000,000 ns). |
| 107 | * This is much less than the 1000 second interval, so the packet |
| 108 | * should still be buffered. |
| 109 | */ |
| 110 | qtest_clock_step(qts, 1000000000LL); |
| 111 | |
| 112 | /* Try to receive with non-blocking - should fail (packet still buffered) */ |
| 113 | ret = recv(recv_sock, recv_buf, sizeof(recv_buf), MSG_DONTWAIT); |
| 114 | g_assert_cmpint(ret, ==, -1); |
| 115 | g_assert(errno == EAGAIN || errno == EWOULDBLOCK); |
| 116 | |
| 117 | /* |
| 118 | * Now change the interval to 1000 us (1ms) via qom-set. |
| 119 | * This should reschedule the timer to fire in 1ms from now. |
| 120 | */ |
| 121 | response = qtest_qmp(qts, |
| 122 | "{'execute': 'qom-set'," |
| 123 | " 'arguments': {" |
| 124 | " 'path': 'fbuf0'," |
| 125 | " 'property': 'interval'," |
| 126 | " 'value': 1000" |
| 127 | "}}"); |
| 128 | g_assert(response); |
| 129 | g_assert(!qdict_haskey(response, "error")); |
| 130 | qobject_unref(response); |
| 131 | |
| 132 | /* |
| 133 | * Advance virtual clock by 2ms (2,000,000 ns). |
| 134 | * This exceeds the new 1ms interval, so the timer should fire |
| 135 | * and release the buffered packet. |
| 136 | * |
| 137 | * If the interval change didn't take effect immediately, we would |
| 138 | * still be waiting for the original 1000 second interval to elapse, |
| 139 | * and the packet would not be released. |
| 140 | */ |
| 141 | qtest_clock_step(qts, 2000000LL); |
| 142 | |
| 143 | /* |
| 144 | * Now we should be able to receive the packet through the redirector. |
| 145 | * The packet was released by filter-buffer and sent to filter-redirector, |
| 146 | * which forwarded it to the chardev socket. |
| 147 | */ |
| 148 | ret = recv(recv_sock, &len, sizeof(len), 0); |
| 149 | g_assert_cmpint(ret, ==, sizeof(len)); |
| 150 | len = ntohl(len); |
| 151 | g_assert_cmpint(len, ==, sizeof(send_buf)); |
| 152 | |
| 153 | ret = recv(recv_sock, recv_buf, len, 0); |
| 154 | g_assert_cmpint(ret, ==, len); |
| 155 | g_assert_cmpstr(recv_buf, ==, send_buf); |
| 156 | |
| 157 | close(recv_sock); |
| 158 | close(backend_sock[0]); |
| 159 | unlink(sock_path); |
| 160 | qtest_quit(qts); |
| 161 | } |
| 162 | |
| 163 | int main(int argc, char **argv) |
| 164 | { |
| 165 | g_test_init(&argc, &argv, NULL); |
| 166 | qtest_add_func("/netfilter/change_interval_timer", |
| 167 | test_change_interval_timer); |
| 168 | return g_test_run(); |
| 169 | } |