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 <lulu@redhat.com>
---
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 c1b9dabacd..ab711e8835 100644
--- a/net/filter-mirror.c
+++ b/net/filter-mirror.c
@@ -267,6 +267,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)
@@ -302,6 +306,27 @@ static void redirector_rs_finalize(SocketReadState *rs)
redirector_to_filter(nf, rs->buf, rs->packet_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)
{
@@ -389,24 +414,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)
@@ -518,6 +549,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