Merge tag 'net-pull-request' of https://github.com/jasowang/qemu into staging

# -----BEGIN PGP SIGNATURE-----
#
# iQEzBAABCAAdFiEEIV1G9IJGaJ7HfzVi7wSWWzmNYhEFAmlzZqsACgkQ7wSWWzmN
# YhGITAf+I46cGYha4dE7Gepbqnk+/eHxURNhToX2yZwWsRBkn4LEWHxavWzYGhTk
# acaVL7zPiHG7S33xSUT7Ie3yrLvbpATAlBsa5xbEKS26KAVIzVtsmMJA6jHyKXUX
# RKBoX2zUkveMZCDtU0XSPjf/wzf7LyeFEDk/o9Agl5zzqfU3mfe58Zk+9MkpFJ9Y
# HEGgocbW4Kuu65RJzesejbrBw0f3PMq8cfktUJ8rj0o5v5MX58hrijBAbE5JLrxG
# Z2u1GvMFR4ZA3e+Mmgu5zg2/AZ4/ZrN9c8moxB9DWLVX8Psz4fJwyYm2Hx0ldhf4
# 4ETQ326nrAZ5REiUTea1FPACBSK7dw==
# =TV7k
# -----END PGP SIGNATURE-----
# gpg: Signature made Fri 23 Jan 2026 11:16:43 PM AEDT
# gpg:                using RSA key 215D46F48246689EC77F3562EF04965B398D6211
# gpg: Good signature from "Jason Wang (Jason Wang on RedHat) <jasowang@redhat.com>" [unknown]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: 215D 46F4 8246 689E C77F  3562 EF04 965B 398D 6211

* tag 'net-pull-request' of https://github.com/jasowang/qemu:
  tests/qtest: Add test for filter-redirector rx event opened
  qtest: add a test to test redirector status change
  net/filter-redirector: add support for dynamic status on/off switching
  tests/qtest: add test for filter-buffer interval change
  net/filter-buffer: make interval change take effect immediately
  net/tap: rework tap_set_sndbuf()
  net/tap: tap_set_sndbuf(): add return value
  net/tap: setup exit notifier only when needed
  net/tap: rework scripts handling
  net/tap: pass NULL to net_init_tap_one() in cases when scripts are NULL
  net/tap: net_init_tap_one(): move parameter checking earlier
  net/tap: net_init_tap_one(): drop extra error propagation
  net/tap-linux.c: avoid abort when setting invalid fd

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
Richard Henderson
2026-01-24 07:58:52 +11:00
11 changed files with 564 additions and 72 deletions

View File

@@ -159,6 +159,7 @@ static void filter_buffer_set_interval(Object *obj, Visitor *v,
Error **errp)
{
FilterBufferState *s = FILTER_BUFFER(obj);
NetFilterState *nf = NETFILTER(obj);
uint32_t value;
if (!visit_type_uint32(v, name, &value, errp)) {
@@ -170,6 +171,11 @@ static void filter_buffer_set_interval(Object *obj, Visitor *v,
return;
}
s->interval = value;
if (nf->netdev && nf->on) {
timer_mod(&s->release_timer,
qemu_clock_get_us(QEMU_CLOCK_VIRTUAL) + s->interval);
}
}
static void filter_buffer_class_init(ObjectClass *oc, const void *data)

View File

@@ -179,9 +179,16 @@ static void redirector_chr_event(void *opaque, QEMUChrEvent event)
MirrorState *s = FILTER_REDIRECTOR(nf);
switch (event) {
case CHR_EVENT_OPENED:
if (nf->on) {
qemu_chr_fe_set_handlers_full(&s->chr_in, redirector_chr_can_read,
redirector_chr_read, redirector_chr_event,
NULL, nf, NULL, false, false);
}
break;
case CHR_EVENT_CLOSED:
qemu_chr_fe_set_handlers(&s->chr_in, NULL, NULL, NULL,
NULL, NULL, NULL, true);
qemu_chr_fe_set_handlers_full(&s->chr_in, NULL, NULL, redirector_chr_event,
NULL, nf, NULL, false, false);
break;
default:
break;
@@ -306,9 +313,11 @@ static void filter_redirector_setup(NetFilterState *nf, Error **errp)
return;
}
qemu_chr_fe_set_handlers(&s->chr_in, redirector_chr_can_read,
redirector_chr_read, redirector_chr_event,
NULL, nf, NULL, true);
if (nf->on) {
qemu_chr_fe_set_handlers(&s->chr_in, redirector_chr_can_read,
redirector_chr_read, redirector_chr_event,
NULL, nf, NULL, true);
}
}
if (s->outdev) {
@@ -324,6 +333,24 @@ static void filter_redirector_setup(NetFilterState *nf, Error **errp)
}
}
static void filter_redirector_status_changed(NetFilterState *nf, Error **errp)
{
MirrorState *s = FILTER_REDIRECTOR(nf);
if (!s->indev) {
return;
}
if (nf->on) {
qemu_chr_fe_set_handlers(&s->chr_in, redirector_chr_can_read,
redirector_chr_read, redirector_chr_event,
NULL, nf, NULL, true);
} else {
qemu_chr_fe_set_handlers(&s->chr_in, NULL, NULL, NULL,
NULL, NULL, NULL, true);
}
}
static char *filter_redirector_get_indev(Object *obj, Error **errp)
{
MirrorState *s = FILTER_REDIRECTOR(obj);
@@ -440,6 +467,7 @@ static void filter_redirector_class_init(ObjectClass *oc, const void *data)
nfc->setup = filter_redirector_setup;
nfc->cleanup = filter_redirector_cleanup;
nfc->receive_iov = filter_redirector_receive_iov;
nfc->status_changed = filter_redirector_status_changed;
}
static void filter_mirror_init(Object *obj)

View File

@@ -206,8 +206,9 @@ error:
}
#endif /* __FreeBSD__ */
void tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp)
bool tap_set_sndbuf(int fd, int sndbuf, Error **errp)
{
return true;
}
int tap_probe_vnet_hdr(int fd, Error **errp)

View File

@@ -143,23 +143,14 @@ int tap_open(char *ifname, int ifname_size, int *vnet_hdr,
* Ethernet NICs generally have txqueuelen=1000, so 1Mb is
* a good value, given a 1500 byte MTU.
*/
#define TAP_DEFAULT_SNDBUF 0
void tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp)
bool tap_set_sndbuf(int fd, int sndbuf, Error **errp)
{
int sndbuf;
sndbuf = !tap->has_sndbuf ? TAP_DEFAULT_SNDBUF :
tap->sndbuf > INT_MAX ? INT_MAX :
tap->sndbuf;
if (!sndbuf) {
sndbuf = INT_MAX;
}
if (ioctl(fd, TUNSETSNDBUF, &sndbuf) == -1 && tap->has_sndbuf) {
if (ioctl(fd, TUNSETSNDBUF, &sndbuf) == -1) {
error_setg_errno(errp, errno, "TUNSETSNDBUF ioctl failed");
return false;
}
return true;
}
int tap_probe_vnet_hdr(int fd, Error **errp)
@@ -214,10 +205,11 @@ bool tap_probe_has_tunnel(int fd)
void tap_fd_set_vnet_hdr_len(int fd, int len)
{
if (ioctl(fd, TUNSETVNETHDRSZ, &len) == -1) {
fprintf(stderr, "TUNSETVNETHDRSZ ioctl() failed: %s. Exiting.\n",
strerror(errno));
abort();
int ret;
ret = ioctl(fd, TUNSETVNETHDRSZ, &len);
if (ret != 0) {
error_report("TUNSETVNETHDRSZ ioctl() failed: %s.", strerror(errno));
}
}

View File

@@ -208,8 +208,9 @@ int tap_open(char *ifname, int ifname_size, int *vnet_hdr,
return fd;
}
void tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp)
bool tap_set_sndbuf(int fd, int sndbuf, Error **errp)
{
return true;
}
int tap_probe_vnet_hdr(int fd, Error **errp)

View File

@@ -33,8 +33,9 @@ int tap_open(char *ifname, int ifname_size, int *vnet_hdr,
return -1;
}
void tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp)
bool tap_set_sndbuf(int fd, int sndbuf, Error **errp)
{
return true;
}
int tap_probe_vnet_hdr(int fd, Error **errp)

View File

@@ -91,6 +91,21 @@ static void launch_script(const char *setup_script, const char *ifname,
static void tap_send(void *opaque);
static void tap_writable(void *opaque);
static char *tap_parse_script(const char *script_arg, const char *default_path)
{
g_autofree char *res = g_strdup(script_arg);
if (!res) {
res = get_relocated_path(default_path);
}
if (res[0] == '\0' || strcmp(res, "no") == 0) {
return NULL;
}
return g_steal_pointer(&res);
}
static void tap_update_fd_handler(TAPState *s)
{
qemu_set_fd_handler(s->fd,
@@ -311,11 +326,9 @@ static void tap_exit_notify(Notifier *notifier, void *data)
TAPState *s = container_of(notifier, TAPState, exit);
Error *err = NULL;
if (s->down_script[0]) {
launch_script(s->down_script, s->down_script_arg, s->fd, &err);
if (err) {
error_report_err(err);
}
launch_script(s->down_script, s->down_script_arg, s->fd, &err);
if (err) {
error_report_err(err);
}
}
@@ -331,8 +344,11 @@ static void tap_cleanup(NetClientState *nc)
qemu_purge_queued_packets(nc);
tap_exit_notify(&s->exit, NULL);
qemu_remove_exit_notifier(&s->exit);
if (s->exit.notify) {
tap_exit_notify(&s->exit, NULL);
qemu_remove_exit_notifier(&s->exit);
s->exit.notify = NULL;
}
tap_read_poll(s, false);
tap_write_poll(s, false);
@@ -428,9 +444,6 @@ static TAPState *net_tap_fd_init(NetClientState *peer,
tap_read_poll(s, true);
s->vhost_net = NULL;
s->exit.notify = tap_exit_notify;
qemu_add_exit_notifier(&s->exit);
return s;
}
@@ -676,9 +689,7 @@ static int net_tap_init(const NetdevTapOptions *tap, int *vnet_hdr,
return -1;
}
if (setup_script &&
setup_script[0] != '\0' &&
strcmp(setup_script, "no") != 0) {
if (setup_script) {
launch_script(setup_script, ifname, fd, &err);
if (err) {
error_propagate(errp, err);
@@ -698,13 +709,14 @@ static void net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer,
const char *downscript, const char *vhostfdname,
int vnet_hdr, int fd, Error **errp)
{
Error *err = NULL;
TAPState *s = net_tap_fd_init(peer, model, name, fd, vnet_hdr);
int vhostfd;
bool sndbuf_required = tap->has_sndbuf;
int sndbuf =
(tap->has_sndbuf && tap->sndbuf) ? MIN(tap->sndbuf, INT_MAX) : INT_MAX;
tap_set_sndbuf(s->fd, tap, &err);
if (err) {
error_propagate(errp, err);
if (!tap_set_sndbuf(fd, sndbuf, sndbuf_required ? errp : NULL) &&
sndbuf_required) {
goto failed;
}
@@ -714,12 +726,14 @@ static void net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer,
qemu_set_info_str(&s->nc, "helper=%s", tap->helper);
} else {
qemu_set_info_str(&s->nc, "ifname=%s,script=%s,downscript=%s", ifname,
script, downscript);
script ?: "no", downscript ?: "no");
if (strcmp(downscript, "no") != 0) {
if (downscript) {
snprintf(s->down_script, sizeof(s->down_script), "%s", downscript);
snprintf(s->down_script_arg, sizeof(s->down_script_arg),
"%s", ifname);
s->exit.notify = tap_exit_notify;
qemu_add_exit_notifier(&s->exit);
}
}
@@ -736,9 +750,8 @@ static void net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer,
}
if (vhostfdname) {
vhostfd = monitor_fd_param(monitor_cur(), vhostfdname, &err);
vhostfd = monitor_fd_param(monitor_cur(), vhostfdname, errp);
if (vhostfd == -1) {
error_propagate(errp, err);
goto failed;
}
if (!qemu_set_blocking(vhostfd, false, errp)) {
@@ -768,9 +781,6 @@ static void net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer,
"vhost-net requested but could not be initialized");
goto failed;
}
} else if (vhostfdname) {
error_setg(errp, "vhostfd(s)= is not valid without vhost");
goto failed;
}
return;
@@ -811,8 +821,6 @@ int net_init_tap(const Netdev *netdev, const char *name,
const NetdevTapOptions *tap;
int fd, vnet_hdr = 0, i = 0, queues;
/* for the no-fd, no-helper case */
const char *script;
const char *downscript;
Error *err = NULL;
const char *vhostfdname;
char ifname[128];
@@ -822,8 +830,6 @@ int net_init_tap(const Netdev *netdev, const char *name,
tap = &netdev->u.tap;
queues = tap->has_queues ? tap->queues : 1;
vhostfdname = tap->vhostfd;
script = tap->script;
downscript = tap->downscript;
/* QEMU hubs do not support multiqueue tap, in this case peer is set.
* For -netdev, peer is always NULL. */
@@ -832,6 +838,11 @@ int net_init_tap(const Netdev *netdev, const char *name,
return -1;
}
if (tap->has_vhost && !tap->vhost && (tap->vhostfds || tap->vhostfd)) {
error_setg(errp, "vhostfd(s)= is not valid without vhost");
return -1;
}
if (tap->fd) {
if (tap->ifname || tap->script || tap->downscript ||
tap->has_vnet_hdr || tap->helper || tap->has_queues ||
@@ -859,7 +870,7 @@ int net_init_tap(const Netdev *netdev, const char *name,
}
net_init_tap_one(tap, peer, "tap", name, NULL,
script, downscript,
NULL, NULL,
vhostfdname, vnet_hdr, fd, &err);
if (err) {
error_propagate(errp, err);
@@ -920,7 +931,7 @@ int net_init_tap(const Netdev *netdev, const char *name,
}
net_init_tap_one(tap, peer, "tap", name, ifname,
script, downscript,
NULL, NULL,
tap->vhostfds ? vhost_fds[i] : NULL,
vnet_hdr, fd, &err);
if (err) {
@@ -965,7 +976,7 @@ free_fail:
}
net_init_tap_one(tap, peer, "bridge", name, ifname,
script, downscript, vhostfdname,
NULL, NULL, vhostfdname,
vnet_hdr, fd, &err);
if (err) {
error_propagate(errp, err);
@@ -973,21 +984,16 @@ free_fail:
return -1;
}
} else {
g_autofree char *default_script = NULL;
g_autofree char *default_downscript = NULL;
g_autofree char *script =
tap_parse_script(tap->script, DEFAULT_NETWORK_SCRIPT);
g_autofree char *downscript =
tap_parse_script(tap->downscript, DEFAULT_NETWORK_DOWN_SCRIPT);
if (tap->vhostfds) {
error_setg(errp, "vhostfds= is invalid if fds= wasn't specified");
return -1;
}
if (!script) {
script = default_script = get_relocated_path(DEFAULT_NETWORK_SCRIPT);
}
if (!downscript) {
downscript = default_downscript =
get_relocated_path(DEFAULT_NETWORK_DOWN_SCRIPT);
}
if (tap->ifname) {
pstrcpy(ifname, sizeof ifname, tap->ifname);
} else {
@@ -995,7 +1001,7 @@ free_fail:
}
for (i = 0; i < queues; i++) {
fd = net_tap_init(tap, &vnet_hdr, i >= 1 ? "no" : script,
fd = net_tap_init(tap, &vnet_hdr, i >= 1 ? NULL : script,
ifname, sizeof ifname, queues > 1, errp);
if (fd == -1) {
return -1;
@@ -1010,8 +1016,8 @@ free_fail:
}
net_init_tap_one(tap, peer, "tap", name, ifname,
i >= 1 ? "no" : script,
i >= 1 ? "no" : downscript,
i >= 1 ? NULL : script,
i >= 1 ? NULL : downscript,
vhostfdname, vnet_hdr, fd, &err);
if (err) {
error_propagate(errp, err);

View File

@@ -26,7 +26,6 @@
#ifndef NET_TAP_INT_H
#define NET_TAP_INT_H
#include "qapi/qapi-types-net.h"
#include "net/net.h"
int tap_open(char *ifname, int ifname_size, int *vnet_hdr,
@@ -34,7 +33,7 @@ int tap_open(char *ifname, int ifname_size, int *vnet_hdr,
ssize_t tap_read_packet(int tapfd, uint8_t *buf, int maxlen);
void tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp);
bool tap_set_sndbuf(int fd, int sndbuf, Error **errp);
int tap_probe_vnet_hdr(int fd, Error **errp);
int tap_probe_has_ufo(int fd);
int tap_probe_has_uso(int fd);

View File

@@ -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'] : [])

View File

@@ -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 <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();
}

View File

@@ -196,10 +196,298 @@ static void test_redirector_rx(void)
qtest_quit(qts);
}
/*
* Test filter-redirector status on/off switching.
*
* This test verifies that:
* 1. When status is set to "off", the filter stops receiving data from indev
* 2. When status is set back to "on", the filter resumes receiving data
*/
static void test_redirector_status(void)
{
int backend_sock[2], send_sock;
uint32_t ret = 0, len = 0;
char send_buf[] = "Hello!!";
char sock_path0[] = "filter-redirector0.XXXXXX";
char *recv_buf;
uint32_t size = sizeof(send_buf);
size = htonl(size);
QTestState *qts;
struct timeval tv;
fd_set rfds;
ret = socketpair(PF_UNIX, SOCK_STREAM, 0, backend_sock);
g_assert_cmpint(ret, !=, -1);
ret = mkstemp(sock_path0);
g_assert_cmpint(ret, !=, -1);
/*
* Setup a simple rx path:
* chardev (sock_path0) -> filter-redirector -> socket backend
*/
qts = qtest_initf(
"-nic socket,id=qtest-bn0,fd=%d "
"-chardev socket,id=redirector0,path=%s,server=on,wait=off "
"-object filter-redirector,id=qtest-f0,netdev=qtest-bn0,"
"queue=rx,indev=redirector0 ",
backend_sock[1], sock_path0);
send_sock = unix_connect(sock_path0, NULL);
g_assert_cmpint(send_sock, !=, -1);
/* send a qmp command to guarantee that 'connected' is setting to true. */
qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}");
struct iovec iov[] = {
{
.iov_base = &size,
.iov_len = sizeof(size),
}, {
.iov_base = send_buf,
.iov_len = sizeof(send_buf),
},
};
/*
* Test 1: Set status to "off" and verify data is not received
*/
qtest_qmp_assert_success(qts,
"{ 'execute': 'qom-set', 'arguments': "
"{ 'path': '/objects/qtest-f0', 'property': 'status', 'value': 'off' }}");
ret = iov_send(send_sock, iov, 2, 0, sizeof(size) + sizeof(send_buf));
g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size));
/*
* Use select with timeout to check if data arrives.
* When status is off, no data should arrive.
*/
FD_ZERO(&rfds);
FD_SET(backend_sock[0], &rfds);
tv.tv_sec = 0;
tv.tv_usec = 500000; /* 500ms timeout */
ret = select(backend_sock[0] + 1, &rfds, NULL, NULL, &tv);
g_assert_cmpint(ret, ==, 0); /* Should timeout, no data */
/*
* Test 2: Set status back to "on" and verify data is received
*/
qtest_qmp_assert_success(qts,
"{ 'execute': 'qom-set', 'arguments': "
"{ 'path': '/objects/qtest-f0', 'property': 'status', 'value': 'on' }}");
ret = iov_send(send_sock, iov, 2, 0, sizeof(size) + sizeof(send_buf));
g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size));
ret = recv(backend_sock[0], &len, sizeof(len), 0);
g_assert_cmpint(ret, ==, sizeof(len));
len = ntohl(len);
g_assert_cmpint(len, ==, sizeof(send_buf));
recv_buf = g_malloc(len);
ret = recv(backend_sock[0], recv_buf, len, 0);
g_assert_cmpint(ret, ==, len);
g_assert_cmpstr(recv_buf, ==, send_buf);
g_free(recv_buf);
close(send_sock);
unlink(sock_path0);
qtest_quit(qts);
}
/*
* Test filter-redirector created with status=off.
*
* This test verifies that when a filter-redirector is created with
* status=off, it does not receive data until status is set to on.
*/
static void test_redirector_init_status_off(void)
{
int backend_sock[2], send_sock;
uint32_t ret = 0, len = 0;
char send_buf[] = "Hello!!";
char sock_path0[] = "filter-redirector0.XXXXXX";
char *recv_buf;
uint32_t size = sizeof(send_buf);
size = htonl(size);
QTestState *qts;
struct timeval tv;
fd_set rfds;
ret = socketpair(PF_UNIX, SOCK_STREAM, 0, backend_sock);
g_assert_cmpint(ret, !=, -1);
ret = mkstemp(sock_path0);
g_assert_cmpint(ret, !=, -1);
/*
* Create filter-redirector with status=off from the start
*/
qts = qtest_initf(
"-nic socket,id=qtest-bn0,fd=%d "
"-chardev socket,id=redirector0,path=%s,server=on,wait=off "
"-object filter-redirector,id=qtest-f0,netdev=qtest-bn0,"
"queue=rx,indev=redirector0,status=off ",
backend_sock[1], sock_path0);
send_sock = unix_connect(sock_path0, NULL);
g_assert_cmpint(send_sock, !=, -1);
qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}");
struct iovec iov[] = {
{
.iov_base = &size,
.iov_len = sizeof(size),
}, {
.iov_base = send_buf,
.iov_len = sizeof(send_buf),
},
};
/*
* Test 1: Filter was created with status=off, data should not be received
*/
ret = iov_send(send_sock, iov, 2, 0, sizeof(size) + sizeof(send_buf));
g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size));
FD_ZERO(&rfds);
FD_SET(backend_sock[0], &rfds);
tv.tv_sec = 0;
tv.tv_usec = 500000;
ret = select(backend_sock[0] + 1, &rfds, NULL, NULL, &tv);
g_assert_cmpint(ret, ==, 0); /* Should timeout, no data */
/*
* Test 2: Set status to "on" and verify data is received
*/
qtest_qmp_assert_success(qts,
"{ 'execute': 'qom-set', 'arguments': "
"{ 'path': '/objects/qtest-f0', 'property': 'status', 'value': 'on' }}");
ret = iov_send(send_sock, iov, 2, 0, sizeof(size) + sizeof(send_buf));
g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size));
ret = recv(backend_sock[0], &len, sizeof(len), 0);
g_assert_cmpint(ret, ==, sizeof(len));
len = ntohl(len);
g_assert_cmpint(len, ==, sizeof(send_buf));
recv_buf = g_malloc(len);
ret = recv(backend_sock[0], recv_buf, len, 0);
g_assert_cmpint(ret, ==, len);
g_assert_cmpstr(recv_buf, ==, send_buf);
g_free(recv_buf);
close(send_sock);
unlink(sock_path0);
qtest_quit(qts);
}
static void test_redirector_rx_event_opened(void)
{
int backend_sock[2], send_sock;
uint32_t ret = 0, len = 0;
char send_buf[] = "Hello!!";
char send_buf2[] = "Hello2!!";
char sock_path0[] = "filter-redirector0.XXXXXX";
char *recv_buf;
uint32_t size = sizeof(send_buf);
uint32_t size2 = sizeof(send_buf2);
size = htonl(size);
size2 = htonl(size2);
QTestState *qts;
ret = socketpair(PF_UNIX, SOCK_STREAM, 0, backend_sock);
g_assert_cmpint(ret, !=, -1);
ret = mkstemp(sock_path0);
g_assert_cmpint(ret, !=, -1);
qts = qtest_initf(
"-nic socket,id=qtest-bn0,fd=%d "
"-chardev socket,id=redirector0,path=%s,server=on,wait=off "
"-object filter-redirector,id=qtest-f0,netdev=qtest-bn0,"
"queue=rx,indev=redirector0 ",
backend_sock[1], sock_path0);
struct iovec iov[] = {
{
.iov_base = &size,
.iov_len = sizeof(size),
}, {
.iov_base = send_buf,
.iov_len = sizeof(send_buf),
},
};
struct iovec iov2[] = {
{
.iov_base = &size2,
.iov_len = sizeof(size2),
}, {
.iov_base = send_buf2,
.iov_len = sizeof(send_buf2),
},
};
/* First connection */
send_sock = unix_connect(sock_path0, NULL);
g_assert_cmpint(send_sock, !=, -1);
qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}");
ret = iov_send(send_sock, iov, 2, 0, sizeof(size) + sizeof(send_buf));
g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size));
ret = recv(backend_sock[0], &len, sizeof(len), 0);
g_assert_cmpint(ret, ==, sizeof(len));
len = ntohl(len);
g_assert_cmpint(len, ==, sizeof(send_buf));
recv_buf = g_malloc(len);
ret = recv(backend_sock[0], recv_buf, len, 0);
g_assert_cmpint(ret, ==, len);
g_assert_cmpstr(recv_buf, ==, send_buf);
g_free(recv_buf);
close(send_sock);
/* Verify disconnection handling if needed, but mainly we want to test Reconnection */
qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}");
/* Second connection */
send_sock = unix_connect(sock_path0, NULL);
g_assert_cmpint(send_sock, !=, -1);
qtest_qmp_assert_success(qts, "{ 'execute' : 'query-status'}");
ret = iov_send(send_sock, iov2, 2, 0, sizeof(size2) + sizeof(send_buf2));
g_assert_cmpint(ret, ==, sizeof(send_buf2) + sizeof(size2));
ret = recv(backend_sock[0], &len, sizeof(len), 0);
g_assert_cmpint(ret, ==, sizeof(len));
len = ntohl(len);
g_assert_cmpint(len, ==, sizeof(send_buf2));
recv_buf = g_malloc(len);
ret = recv(backend_sock[0], recv_buf, len, 0);
g_assert_cmpint(ret, ==, len);
g_assert_cmpstr(recv_buf, ==, send_buf2);
g_free(recv_buf);
close(send_sock);
unlink(sock_path0);
qtest_quit(qts);
close(backend_sock[0]);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
qtest_add_func("/netfilter/redirector_tx", test_redirector_tx);
qtest_add_func("/netfilter/redirector_rx", test_redirector_rx);
qtest_add_func("/netfilter/redirector_status", test_redirector_status);
qtest_add_func("/netfilter/redirector_init_status_off",
test_redirector_init_status_off);
qtest_add_func("/netfilter/redirector_rx_event_opened",
test_redirector_rx_event_opened);
return g_test_run();
}