On Wed, Feb 25, 2026 at 2:19 PM Cindy Lu <[email protected]> wrote: > > On Sat, Feb 14, 2026 at 10:46 AM Zhang Chen <[email protected]> wrote: > > > > 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 > > > Hi Chen > For this RFC, filter-reply and filter-rewriter is not need, I will add > support for them later.
Right, I think the reason is that we don't see a use case for them. Thanks > Thanks > cindy > > . > > > > > > > > > > > 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 > > > > > >
