On Sat, Feb 7, 2026 at 9:47 PM Cindy Lu <[email protected]> wrote: > > 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 >
It seems this series is trying to address previous VM migration network related issues that Juraj Marcin mentioned. Add CC to him. I've taken a quick look, and this series lacks support for filter-reply and filter-rewriter. Could there be a reason for this? Thanks Chen . > 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 >
