From: Jason Wang <[email protected]>
Add a QAPI option 'enable_when_stopped' for filter-redirector that
allows it to continue receiving data from netdev when VM is stopped.
When enabled, a VM state change handler activates the netdev's
read_poll() when VM stops, allowing the netdev to continue reading
data and passing it through filter-redirector to the chardev output.
This is useful for scenarios where filter-redirector needs to
process network traffic even when the VM is paused.
Usage:
-object filter-redirector,id=f0,netdev=net0,queue=tx,\
outdev=chardev0,enable_when_stopped=true
Signed-off-by: Jason Wang <[email protected]>
Signed-off-by: Cindy Lu <[email protected]>
---
net/filter-mirror.c | 38 ++++++++++
qapi/qom.json | 7 +-
tests/qtest/test-filter-redirector.c | 106 +++++++++++++++++++++++++++
3 files changed, 150 insertions(+), 1 deletion(-)
diff --git a/net/filter-mirror.c b/net/filter-mirror.c
index aa2ab452fd..e1a0650abd 100644
--- a/net/filter-mirror.c
+++ b/net/filter-mirror.c
@@ -21,6 +21,7 @@
#include "qemu/iov.h"
#include "qemu/sockets.h"
#include "block/aio-wait.h"
+#include "system/runstate.h"
#define TYPE_FILTER_MIRROR "filter-mirror"
typedef struct MirrorState MirrorState;
@@ -41,6 +42,8 @@ struct MirrorState {
CharFrontend chr_out;
SocketReadState rs;
bool vnet_hdr;
+ bool enable_when_stopped;
+ VMChangeStateEntry *vmsentry;
};
typedef struct FilterSendCo {
@@ -251,6 +254,7 @@ static void filter_redirector_cleanup(NetFilterState *nf)
qemu_chr_fe_deinit(&s->chr_in, false);
qemu_chr_fe_deinit(&s->chr_out, false);
+ qemu_del_vm_change_state_handler(s->vmsentry);
}
static void filter_mirror_setup(NetFilterState *nf, Error **errp)
@@ -282,6 +286,18 @@ static void redirector_rs_finalize(SocketReadState *rs)
redirector_to_filter(nf, rs->buf, rs->packet_len);
}
+static void filter_redirector_vm_state_change(void *opaque, bool running,
+ RunState state)
+{
+ NetFilterState *nf = opaque;
+ MirrorState *s = FILTER_REDIRECTOR(nf);
+ NetClientState *nc = nf->netdev;
+
+ if (!running && s->enable_when_stopped && nc->info->read_poll) {
+ nc->info->read_poll(nc, true);
+ }
+}
+
static void filter_redirector_setup(NetFilterState *nf, Error **errp)
{
MirrorState *s = FILTER_REDIRECTOR(nf);
@@ -331,6 +347,9 @@ static void filter_redirector_setup(NetFilterState *nf,
Error **errp)
return;
}
}
+
+ s->vmsentry = qemu_add_vm_change_state_handler(
+ filter_redirector_vm_state_change, nf);
}
static void filter_redirector_status_changed(NetFilterState *nf, Error **errp)
@@ -437,6 +456,22 @@ static void filter_redirector_set_vnet_hdr(Object *obj,
s->vnet_hdr = value;
}
+static bool filter_redirector_get_enable_when_stopped(Object *obj, Error
**errp)
+{
+ MirrorState *s = FILTER_REDIRECTOR(obj);
+
+ return s->enable_when_stopped;
+}
+
+static void filter_redirector_set_enable_when_stopped(Object *obj,
+ bool value,
+ Error **errp)
+{
+ MirrorState *s = FILTER_REDIRECTOR(obj);
+
+ s->enable_when_stopped = value;
+}
+
static void filter_mirror_class_init(ObjectClass *oc, const void *data)
{
NetFilterClass *nfc = NETFILTER_CLASS(oc);
@@ -463,6 +498,9 @@ static void filter_redirector_class_init(ObjectClass *oc,
const void *data)
object_class_property_add_bool(oc, "vnet_hdr_support",
filter_redirector_get_vnet_hdr,
filter_redirector_set_vnet_hdr);
+ object_class_property_add_bool(oc, "enable_when_stopped",
+ filter_redirector_get_enable_when_stopped,
+ filter_redirector_set_enable_when_stopped);
nfc->setup = filter_redirector_setup;
nfc->cleanup = filter_redirector_cleanup;
diff --git a/qapi/qom.json b/qapi/qom.json
index 6f5c9de0f0..b1fe55944c 100644
--- a/qapi/qom.json
+++ b/qapi/qom.json
@@ -488,13 +488,18 @@
# @vnet_hdr_support: if true, vnet header support is enabled
# (default: false)
#
+# @enable_when_stopped: if true, activate netdev's read poll when VM
+# stops to allow filter-redirector to continue receiving data
+# (default: false)
+#
# Since: 2.6
##
{ 'struct': 'FilterRedirectorProperties',
'base': 'NetfilterProperties',
'data': { '*indev': 'str',
'*outdev': 'str',
- '*vnet_hdr_support': 'bool' } }
+ '*vnet_hdr_support': 'bool',
+ '*enable_when_stopped': 'bool' } }
##
# @FilterRewriterProperties:
diff --git a/tests/qtest/test-filter-redirector.c
b/tests/qtest/test-filter-redirector.c
index 5540c232c0..fa958bb0a5 100644
--- a/tests/qtest/test-filter-redirector.c
+++ b/tests/qtest/test-filter-redirector.c
@@ -385,6 +385,110 @@ static void test_redirector_init_status_off(void)
qtest_quit(qts);
}
+/*
+ * Test filter-redirector works when VM is stopped (TX direction).
+ *
+ * This test verifies that when VM is stopped, the filter-redirector
+ * can still receive data from the netdev because the VM state change
+ * handler activates the netdev's read_poll(). Data flows from netdev
+ * to filter-redirector and out through the chardev.
+ *
+ * Data flow: backend_sock -> netdev (read_poll) -> filter-redirector ->
chardev
+ */
+static void test_redirector_with_vm_stop(void)
+{
+ int backend_sock[2], recv_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 TX path: netdev -> filter-redirector -> chardev
+ * Data sent to backend_sock[0] will be read by the netdev,
+ * passed through filter-redirector, and output to sock_path0.
+ * Enable enable_when_stopped to activate read_poll when VM stops.
+ */
+ 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=tx,outdev=redirector0,enable_when_stopped=true ",
+ backend_sock[1], sock_path0);
+
+ recv_sock = unix_connect(sock_path0, NULL);
+ g_assert_cmpint(recv_sock, !=, -1);
+
+ /* Ensure connection is established */
+ 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),
+ },
+ };
+
+ /*
+ * Stop the VM - this triggers our vm_state_change handler
+ * which should activate the netdev's read_poll()
+ */
+ qtest_qmp_assert_success(qts, "{ 'execute' : 'stop'}");
+
+ /* Wait for VM to actually stop */
+ qtest_qmp_eventwait(qts, "STOP");
+
+ /*
+ * Send data to backend socket while VM is stopped.
+ * The netdev should still read the data (because read_poll is
+ * activated by our vm_state_change handler), pass it through
+ * filter-redirector, and output to the chardev.
+ */
+ ret = iov_send(backend_sock[0], iov, 2, 0, sizeof(size) +
sizeof(send_buf));
+ g_assert_cmpint(ret, ==, sizeof(send_buf) + sizeof(size));
+
+ /*
+ * Data should arrive at recv_sock even with VM stopped,
+ * because read_poll is activated.
+ */
+ FD_ZERO(&rfds);
+ FD_SET(recv_sock, &rfds);
+ tv.tv_sec = 5;
+ tv.tv_usec = 0;
+ ret = select(recv_sock + 1, &rfds, NULL, NULL, &tv);
+ g_assert_cmpint(ret, ==, 1); /* Data should arrive */
+
+ ret = recv(recv_sock, &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(recv_sock, recv_buf, len, 0);
+ g_assert_cmpint(ret, ==, len);
+ g_assert_cmpstr(recv_buf, ==, send_buf);
+
+ g_free(recv_buf);
+ close(recv_sock);
+ close(backend_sock[0]);
+ unlink(sock_path0);
+ qtest_quit(qts);
+}
+
static void test_redirector_rx_event_opened(void)
{
int backend_sock[2], send_sock;
@@ -489,5 +593,7 @@ int main(int argc, char **argv)
test_redirector_init_status_off);
qtest_add_func("/netfilter/redirector_rx_event_opened",
test_redirector_rx_event_opened);
+ qtest_add_func("/netfilter/redirector_with_vm_stop",
+ test_redirector_with_vm_stop);
return g_test_run();
}
--
2.52.0