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
> > >
> >
>


Reply via email to