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
