During migration pre-switchover the VM runstate is stopped, which can cause tap 
read_poll to be disabled (qemu_send_packet_async() returns 0) and incoming tap 
frames stop reaching the redirector/mirror filter.

Re-enable tap read_poll when swredir is turned on while stopped, and allow the 
redirector outdev path to keep draining packets while stopped by adding a 
per-NetClientState allow_send_when_stopped bit used by qemu_can_send_packet().

Signed-off-by: Cindy Lu <[email protected]>
---
 include/net/net.h   |  6 ++++++
 net/filter-mirror.c | 49 +++++++++++++++++++++++++++++++++++++--------
 net/net.c           | 10 ++++++++-
 3 files changed, 56 insertions(+), 9 deletions(-)

diff --git a/include/net/net.h b/include/net/net.h
index d2b2e6fc44..bde052acbb 100644
--- a/include/net/net.h
+++ b/include/net/net.h
@@ -126,6 +126,12 @@ struct NetClientState {
     char *name;
     char info_str[256];
     unsigned receive_disabled : 1;
+    /*
+     * Allow this sender to keep draining packets while VM is stopped.
+     * Used by COLO-style pre-switchover mirroring, where netfilter needs to
+     * see incoming tap frames even when runstate is not running.
+     */
+    unsigned allow_send_when_stopped:1;
     NetClientDestructor *destructor;
     unsigned int queue_index;
     unsigned rxfilter_notify_enabled:1;
diff --git a/net/filter-mirror.c b/net/filter-mirror.c
index de32028246..ad99bebec6 100644
--- a/net/filter-mirror.c
+++ b/net/filter-mirror.c
@@ -268,6 +268,10 @@ 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);
+
+    if (nf->netdev) {
+        nf->netdev->allow_send_when_stopped = 0;
+    }
 }
 
 static void filter_mirror_setup(NetFilterState *nf, Error **errp)
@@ -322,6 +326,27 @@ static void redirector_rs_finalize(SocketReadState *rs)
     redirector_to_filter(nf, QEMU_NET_PACKET_FLAG_RAW, buf, len);
 }
 
+static void
+filter_redirector_refresh_allow_send_when_stopped(NetFilterState *nf)
+{
+    MirrorState *s = FILTER_REDIRECTOR(nf);
+    NetClientState *nc = nf->netdev;
+
+    if (!nc) {
+        return;
+    }
+
+    /*
+     * Allow sending when stopped if enable_when_stopped is set and we have
+     * an outdev. This must be independent of nf->on (status) so that packets
+     * can still flow through the filter chain to other filters even when this
+     * redirector is disabled. Otherwise, tap_send() will disable read_poll
+     * when qemu_can_send_packet() returns false, preventing further packet
+     * processing.
+     */
+    nc->allow_send_when_stopped = (s->enable_when_stopped && s->outdev);
+}
+
 static void filter_redirector_vm_state_change(void *opaque, bool running,
                                               RunState state)
 {
@@ -409,24 +434,30 @@ static void filter_redirector_setup(NetFilterState *nf, 
Error **errp)
         filter_redirector_vm_state_change, nf);
 
     filter_redirector_maybe_enable_read_poll(nf);
+
+    filter_redirector_refresh_allow_send_when_stopped(nf);
 }
 
 static void filter_redirector_status_changed(NetFilterState *nf, Error **errp)
 {
     MirrorState *s = FILTER_REDIRECTOR(nf);
 
-    if (!s->indev) {
-        return;
+    if (s->indev) {
+        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);
+        }
     }
 
     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);
+        filter_redirector_maybe_enable_read_poll(nf);
     }
+
+    filter_redirector_refresh_allow_send_when_stopped(nf);
 }
 
 static char *filter_redirector_get_indev(Object *obj, Error **errp)
@@ -538,6 +569,8 @@ static void 
filter_redirector_set_enable_when_stopped(Object *obj,
     if (value) {
         filter_redirector_maybe_enable_read_poll(nf);
     }
+
+    filter_redirector_refresh_allow_send_when_stopped(nf);
 }
 
 static void filter_mirror_class_init(ObjectClass *oc, const void *data)
diff --git a/net/net.c b/net/net.c
index b15f4db65e..06f54eebb4 100644
--- a/net/net.c
+++ b/net/net.c
@@ -632,7 +632,7 @@ int qemu_can_send_packet(NetClientState *sender)
 {
     int vm_running = runstate_is_running();
 
-    if (!vm_running) {
+    if (!vm_running && !sender->allow_send_when_stopped) {
         return 0;
     }
 
@@ -640,6 +640,14 @@ int qemu_can_send_packet(NetClientState *sender)
         return 1;
     }
 
+    /*
+     * When VM is stopped (e.g. migration pre-switchover), allow draining tap
+     * packets so netfilters (redirector/mirror) can operate.
+     */
+    if (!vm_running && sender->allow_send_when_stopped) {
+        return 1;
+    }
+
     return qemu_can_receive_packet(sender->peer);
 }
 
-- 
2.52.0


Reply via email to