From: Jason Wang <[email protected]> When the VM is stopped, reading from the backend (e.g. tap) can pull host traffic into QEMU queues at a point where the core networking code may flush or purge queued packets on vmstop to avoid state changes while not running. This can show up as intermittent packet loss around stop/resume transitions.
Register a virtio-net vm change state handler that disables backend read_poll() while the VM is stopped, and re-enables it when the VM runs again. Use a higher priority so it runs early on vmstop, while allowing netfilter pipelines (e.g. redirector with enable_when_stopped) to re-enable polling explicitly when they are ready to handle traffic. Signed-off-by: Jason Wang <[email protected]> Signed-off-by: Cindy Lu <[email protected]> --- hw/net/virtio-net.c | 47 ++++++++++++++++++++++++++++++++++ include/hw/virtio/virtio-net.h | 2 ++ 2 files changed, 49 insertions(+) diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c index 17ed0ef919..d6d2188863 100644 --- a/hw/net/virtio-net.c +++ b/hw/net/virtio-net.c @@ -41,6 +41,7 @@ #include "standard-headers/linux/ethtool.h" #include "system/system.h" #include "system/replay.h" +#include "system/runstate.h" #include "trace.h" #include "monitor/qdev.h" #include "monitor/monitor.h" @@ -1310,6 +1311,38 @@ static void virtio_net_disable_rss(VirtIONet *n) virtio_net_commit_rss_config(n); } +static void virtio_net_backend_read_poll_set(VirtIONet *n, bool enable) +{ + int i; + + if (!n->nic) { + return; + } + + for (i = 0; i < (int)n->max_ncs; i++) { + NetClientState *frontend = qemu_get_subqueue(n->nic, i); + NetClientState *backend = frontend ? frontend->peer : NULL; + + if (backend && backend->info && backend->info->read_poll) { + backend->info->read_poll(backend, enable); + } + } +} + +static void virtio_net_vm_state_change_backend_poll(void *opaque, bool running, + RunState state) +{ + VirtIONet *n = opaque; + + /* + * Prevent backend (e.g. tap) from reading packets while the VM is stopped. + * This avoids pulling host->guest traffic into QEMU queues that may be + * purged on vmstop, and lets netfilter pipelines (e.g. redirector+buffer) + * explicitly re-enable polling when they are ready. + */ + virtio_net_backend_read_poll_set(n, running); +} + static bool virtio_net_load_ebpf_fds(VirtIONet *n, Error **errp) { int fds[EBPF_RSS_MAX_FDS] = { [0 ... EBPF_RSS_MAX_FDS - 1] = -1}; @@ -4047,6 +4080,15 @@ static void virtio_net_device_realize(DeviceState *dev, Error **errp) n->rss_data.specified_hash_types.on_bits | n->rss_data.specified_hash_types.auto_bits; } + + /* + * Default behavior: use a higher priority than netfilter redirector + * handlers (default 0) so that on vmstop we disable first, and redirectors + * can re-enable read_poll after this if enable_when_stopped is set. + */ + n->backend_poll_vmstate = + qemu_add_vm_change_state_handler_prio( + virtio_net_vm_state_change_backend_poll, n, 10); } static void virtio_net_device_unrealize(DeviceState *dev) @@ -4059,6 +4101,11 @@ static void virtio_net_device_unrealize(DeviceState *dev) virtio_net_unload_ebpf(n); } + if (n->backend_poll_vmstate) { + qemu_del_vm_change_state_handler(n->backend_poll_vmstate); + n->backend_poll_vmstate = NULL; + } + /* This will stop vhost backend if appropriate. */ virtio_net_set_status(vdev, 0); diff --git a/include/hw/virtio/virtio-net.h b/include/hw/virtio/virtio-net.h index f708355306..a40a7d7cc6 100644 --- a/include/hw/virtio/virtio-net.h +++ b/include/hw/virtio/virtio-net.h @@ -218,6 +218,8 @@ struct VirtIONet { uint64_t saved_guest_offloads; AnnounceTimer announce_timer; bool needs_vnet_hdr_swap; + /* Keep backend (e.g. tap) from reading while VM is stopped. */ + VMChangeStateEntry *backend_poll_vmstate; bool mtu_bypass_backend; /* primary failover device is hidden*/ bool failover_primary_hidden; -- 2.52.0
