{"id":2198121,"url":"http://patchwork.ozlabs.org/api/1.0/patches/2198121/?format=json","project":{"id":14,"url":"http://patchwork.ozlabs.org/api/1.0/projects/14/?format=json","name":"QEMU Development","link_name":"qemu-devel","list_id":"qemu-devel.nongnu.org","list_email":"qemu-devel@nongnu.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20260219115530.2019498-6-vsementsov@yandex-team.ru>","date":"2026-02-19T11:55:26","name":"[v11,5/8] virtio-net: support local migration of backend","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"25f7cfd0b4e010d8803bb8827086719e606026b1","submitter":{"id":84116,"url":"http://patchwork.ozlabs.org/api/1.0/people/84116/?format=json","name":"Vladimir Sementsov-Ogievskiy","email":"vsementsov@yandex-team.ru"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/qemu-devel/patch/20260219115530.2019498-6-vsementsov@yandex-team.ru/mbox/","series":[{"id":492667,"url":"http://patchwork.ozlabs.org/api/1.0/series/492667/?format=json","date":"2026-02-19T11:55:27","name":"virtio-net: live-TAP local migration","version":11,"mbox":"http://patchwork.ozlabs.org/series/492667/mbox/"}],"check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2198121/checks/","tags":{},"headers":{"Return-Path":"<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>","X-Original-To":"incoming@patchwork.ozlabs.org","Delivered-To":"patchwork-incoming@legolas.ozlabs.org","Authentication-Results":["legolas.ozlabs.org;\n\tdkim=pass (1024-bit key;\n unprotected) header.d=yandex-team.ru header.i=@yandex-team.ru\n header.a=rsa-sha256 header.s=default header.b=Hqx/xqxh;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org\n (client-ip=209.51.188.17; helo=lists.gnu.org;\n envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n receiver=patchwork.ozlabs.org)","mail-nwsmtp-smtp-corp-main-34.sas.yp-c.yandex.net;\n dkim=pass header.i=@yandex-team.ru"],"Received":["from lists.gnu.org (lists.gnu.org [209.51.188.17])\n\t(using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fGsKs0fspz1xpY\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 19 Feb 2026 22:56:33 +1100 (AEDT)","from localhost ([::1] helo=lists1p.gnu.org)\n\tby lists.gnu.org with esmtp (Exim 4.90_1)\n\t(envelope-from <qemu-devel-bounces@nongnu.org>)\n\tid 1vt2dO-0004Pu-2T; Thu, 19 Feb 2026 06:55:54 -0500","from eggs.gnu.org ([2001:470:142:3::10])\n by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <vsementsov@yandex-team.ru>)\n id 1vt2dE-0004Nu-Vz\n for qemu-devel@nongnu.org; Thu, 19 Feb 2026 06:55:46 -0500","from forwardcorp1b.mail.yandex.net ([178.154.239.136])\n by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <vsementsov@yandex-team.ru>)\n id 1vt2dB-0006E5-Cy\n for qemu-devel@nongnu.org; Thu, 19 Feb 2026 06:55:43 -0500","from mail-nwsmtp-smtp-corp-main-34.sas.yp-c.yandex.net\n (mail-nwsmtp-smtp-corp-main-34.sas.yp-c.yandex.net\n [IPv6:2a02:6b8:c24:fa2:0:640:41ee:0])\n by forwardcorp1b.mail.yandex.net (Yandex) with ESMTPS id EE8C080873;\n Thu, 19 Feb 2026 14:55:39 +0300 (MSK)","from vsementsov-lin (unknown [2a02:6bf:8080:192::1:26])\n by mail-nwsmtp-smtp-corp-main-34.sas.yp-c.yandex.net (smtpcorp/Yandex) with\n ESMTPSA id VtZwpM4A6uQ0-SsXgb4DO; Thu, 19 Feb 2026 14:55:39 +0300"],"Precedence":"bulk","X-Yandex-Fwd":"1","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=yandex-team.ru;\n s=default; t=1771502139;\n bh=AMAsTQ/pXydwoezlS2DzP9PDH9zAQKktMXlUO0W6ulw=;\n h=Message-ID:Date:In-Reply-To:Cc:Subject:References:To:From;\n b=Hqx/xqxhllrd/nN6Nuvvu+BZTVvRUjITSIsjetd7k+Xy1rWS8oYk7qnc6BpD3TxbK\n oYwKva92xXiQSGSnd3K6SrrZYg2rvsopSQArMikGZ/ZzLIk7ma3fyt5XwURzETBhsX\n HrAcv/sZy+3v8l8vt/0HLVaweFr3zFjcETRa3Vr8=","From":"Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>","To":"jasowang@redhat.com,\n\tmst@redhat.com","Cc":"thuth@redhat.com, armbru@redhat.com, eblake@redhat.com, farosas@suse.de,\n peterx@redhat.com, zhao1.liu@intel.com, wangyanan55@huawei.com,\n philmd@linaro.org, marcel.apfelbaum@gmail.com, eduardo@habkost.net,\n davydov-max@yandex-team.ru, qemu-devel@nongnu.org,\n vsementsov@yandex-team.ru, yc-core@yandex-team.ru, leiyang@redhat.com,\n raphael.s.norwitz@gmail.com, bchaney@akamai.com","Subject":"[PATCH v11 5/8] virtio-net: support local migration of backend","Date":"Thu, 19 Feb 2026 14:55:26 +0300","Message-ID":"<20260219115530.2019498-6-vsementsov@yandex-team.ru>","X-Mailer":"git-send-email 2.52.0","In-Reply-To":"<20260219115530.2019498-1-vsementsov@yandex-team.ru>","References":"<20260219115530.2019498-1-vsementsov@yandex-team.ru>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Received-SPF":"pass client-ip=178.154.239.136;\n envelope-from=vsementsov@yandex-team.ru; helo=forwardcorp1b.mail.yandex.net","X-Spam_score_int":"-20","X-Spam_score":"-2.1","X-Spam_bar":"--","X-Spam_report":"(-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1,\n DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1,\n RCVD_IN_VALIDITY_CERTIFIED_BLOCKED=0.001,\n RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001,\n SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no","X-Spam_action":"no action","X-BeenThere":"qemu-devel@nongnu.org","X-Mailman-Version":"2.1.29","List-Id":"qemu development <qemu-devel.nongnu.org>","List-Unsubscribe":"<https://lists.nongnu.org/mailman/options/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>","List-Archive":"<https://lists.nongnu.org/archive/html/qemu-devel>","List-Post":"<mailto:qemu-devel@nongnu.org>","List-Help":"<mailto:qemu-devel-request@nongnu.org?subject=help>","List-Subscribe":"<https://lists.nongnu.org/mailman/listinfo/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=subscribe>","Errors-To":"qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org","Sender":"qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org"},"content":"Add virtio-net option local-migration, which is true by default,\nbut false for older machine types, which doesn't support the feature.\n\nWhen both global migration parameter \"local\" and new virtio-net\nparameter \"local-migration\" are true, virtio-net transfer the whole\nnet backend to the destination, including open file descriptors.\nOf-course, its only for local migration and the channel must be\nUNIX domain socket.\n\nThis way management tool should not care about creating new TAP, and\nshould not handle switching to it. Migration downtime become shorter.\n\nSupport for TAP will come in the next commit.\n\nSigned-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>\n---\n hw/core/machine.c              |   1 +\n hw/net/virtio-net.c            | 137 ++++++++++++++++++++++++++++++++-\n include/hw/virtio/virtio-net.h |   2 +\n include/net/net.h              |   2 +\n 4 files changed, 141 insertions(+), 1 deletion(-)","diff":"diff --git a/hw/core/machine.c b/hw/core/machine.c\nindex d4ef620c17..b35d0d204b 100644\n--- a/hw/core/machine.c\n+++ b/hw/core/machine.c\n@@ -40,6 +40,7 @@\n \n GlobalProperty hw_compat_10_2[] = {\n     { \"scsi-block\", \"migrate-pr\", \"off\" },\n+    { TYPE_VIRTIO_NET, \"local-migration\", \"false\" },\n };\n const size_t hw_compat_10_2_len = G_N_ELEMENTS(hw_compat_10_2);\n \ndiff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c\nindex 512a7c02c9..9a0b5d2489 100644\n--- a/hw/net/virtio-net.c\n+++ b/hw/net/virtio-net.c\n@@ -38,8 +38,10 @@\n #include \"qapi/qapi-events-migration.h\"\n #include \"hw/virtio/virtio-access.h\"\n #include \"migration/misc.h\"\n+#include \"migration/options.h\"\n #include \"standard-headers/linux/ethtool.h\"\n #include \"system/system.h\"\n+#include \"system/runstate.h\"\n #include \"system/replay.h\"\n #include \"trace.h\"\n #include \"monitor/qdev.h\"\n@@ -3061,7 +3063,17 @@ static void virtio_net_set_multiqueue(VirtIONet *n, int multiqueue)\n     n->multiqueue = multiqueue;\n     virtio_net_change_num_queues(n, max * 2 + 1);\n \n-    virtio_net_set_queue_pairs(n);\n+    /*\n+     * virtio_net_set_multiqueue() called from set_features(0) on early\n+     * reset, when peer may wait for incoming (and is not initialized\n+     * yet).\n+     * Don't worry about it: virtio_net_set_queue_pairs() will be called\n+     * later form virtio_net_post_load_device(), and anyway will be\n+     * noop for local incoming migration with live backend passing.\n+     */\n+    if (!n->peers_wait_incoming) {\n+        virtio_net_set_queue_pairs(n);\n+    }\n }\n \n static int virtio_net_pre_load_queues(VirtIODevice *vdev, uint32_t n)\n@@ -3090,6 +3102,17 @@ static void virtio_net_get_features(VirtIODevice *vdev, uint64_t *features,\n \n     virtio_add_feature_ex(features, VIRTIO_NET_F_MAC);\n \n+    if (n->peers_wait_incoming) {\n+        /*\n+         * Excessive feature set is OK for early initialization when\n+         * we wait for local incoming migration: actual guest-negotiated\n+         * features will come with migration stream anyway. And we are sure\n+         * that we support same host-features as source, because the backend\n+         * is the same (the same TAP device, for example).\n+         */\n+        return;\n+    }\n+\n     if (!peer_has_vnet_hdr(n)) {\n         virtio_clear_feature_ex(features, VIRTIO_NET_F_CSUM);\n         virtio_clear_feature_ex(features, VIRTIO_NET_F_HOST_TSO4);\n@@ -3181,6 +3204,18 @@ static void virtio_net_get_features(VirtIODevice *vdev, uint64_t *features,\n     }\n }\n \n+static bool virtio_net_update_host_features(VirtIONet *n, Error **errp)\n+{\n+    ERRP_GUARD();\n+    VirtIODevice *vdev = VIRTIO_DEVICE(n);\n+\n+    peer_test_vnet_hdr(n);\n+\n+    virtio_net_get_features(vdev, &vdev->host_features, errp);\n+\n+    return !*errp;\n+}\n+\n static int virtio_net_post_load_device(void *opaque, int version_id)\n {\n     VirtIONet *n = opaque;\n@@ -3302,6 +3337,9 @@ struct VirtIONetMigTmp {\n     uint16_t        curr_queue_pairs_1;\n     uint8_t         has_ufo;\n     uint32_t        has_vnet_hdr;\n+\n+    NetClientState *ncs;\n+    uint32_t max_queue_pairs;\n };\n \n /* The 2nd and subsequent tx_waiting flags are loaded later than\n@@ -3571,6 +3609,57 @@ static const VMStateDescription vhost_user_net_backend_state = {\n     }\n };\n \n+static bool virtio_net_migrate_local(void *opaque, int version_id)\n+{\n+    VirtIONet *n = opaque;\n+\n+    return migrate_local() && n->local_migration;\n+}\n+\n+static int virtio_net_nic_pre_save(void *opaque)\n+{\n+    struct VirtIONetMigTmp *tmp = opaque;\n+\n+    tmp->ncs = tmp->parent->nic->ncs;\n+    tmp->max_queue_pairs = tmp->parent->max_queue_pairs;\n+\n+    return 0;\n+}\n+\n+static int virtio_net_nic_pre_load(void *opaque)\n+{\n+    /* Reuse the pointer setup from save */\n+    virtio_net_nic_pre_save(opaque);\n+\n+    return 0;\n+}\n+\n+static int virtio_net_nic_post_load(void *opaque, int version_id)\n+{\n+    struct VirtIONetMigTmp *tmp = opaque;\n+    Error *local_err = NULL;\n+\n+    if (!virtio_net_update_host_features(tmp->parent, &local_err)) {\n+        error_report_err(local_err);\n+        return -EINVAL;\n+    }\n+\n+    return 0;\n+}\n+\n+static const VMStateDescription vmstate_virtio_net_nic = {\n+    .name      = \"virtio-net-nic\",\n+    .pre_load  = virtio_net_nic_pre_load,\n+    .pre_save  = virtio_net_nic_pre_save,\n+    .post_load  = virtio_net_nic_post_load,\n+    .fields    = (const VMStateField[]) {\n+        VMSTATE_VARRAY_UINT32(ncs, struct VirtIONetMigTmp,\n+                              max_queue_pairs, 0, vmstate_net_peer_backend,\n+                              NetClientState),\n+        VMSTATE_END_OF_LIST()\n+    },\n+};\n+\n static const VMStateDescription vmstate_virtio_net_device = {\n     .name = \"virtio-net-device\",\n     .version_id = VIRTIO_NET_VM_VERSION,\n@@ -3602,6 +3691,9 @@ static const VMStateDescription vmstate_virtio_net_device = {\n          * but based on the uint.\n          */\n         VMSTATE_BUFFER_POINTER_UNSAFE(vlans, VirtIONet, 0, MAX_VLAN >> 3),\n+        VMSTATE_WITH_TMP_TEST(VirtIONet, virtio_net_migrate_local,\n+                              struct VirtIONetMigTmp,\n+                              vmstate_virtio_net_nic),\n         VMSTATE_WITH_TMP(VirtIONet, struct VirtIONetMigTmp,\n                          vmstate_virtio_net_has_vnet),\n         VMSTATE_UINT8(mac_table.multi_overflow, VirtIONet),\n@@ -3866,6 +3958,42 @@ static bool failover_hide_primary_device(DeviceListener *listener,\n     return qatomic_read(&n->failover_primary_hidden);\n }\n \n+static bool virtio_net_check_peers_wait_incoming(VirtIONet *n, bool *waiting,\n+                                                 Error **errp)\n+{\n+    bool has_waiting = false;\n+    bool has_not_waiting = false;\n+\n+    for (int i = 0; i < n->max_queue_pairs; i++) {\n+        NetClientState *peer = n->nic->ncs[i].peer;\n+        if (!peer) {\n+            continue;\n+        }\n+\n+        if (peer->info->is_wait_incoming &&\n+            peer->info->is_wait_incoming(peer)) {\n+            has_waiting = true;\n+        } else {\n+            has_not_waiting = true;\n+        }\n+\n+        if (has_waiting && has_not_waiting) {\n+            error_setg(errp, \"Mixed peer states: some peers wait for incoming \"\n+                       \"migration while others don't\");\n+            return false;\n+        }\n+    }\n+\n+    if (has_waiting && !runstate_check(RUN_STATE_INMIGRATE)) {\n+        error_setg(errp, \"Peers wait for incoming, but it's not an incoming \"\n+                   \"migration.\");\n+        return false;\n+    }\n+\n+    *waiting = has_waiting;\n+    return true;\n+}\n+\n static void virtio_net_device_realize(DeviceState *dev, Error **errp)\n {\n     VirtIODevice *vdev = VIRTIO_DEVICE(dev);\n@@ -4003,6 +4131,12 @@ static void virtio_net_device_realize(DeviceState *dev, Error **errp)\n         n->nic->ncs[i].do_not_pad = true;\n     }\n \n+    if (!virtio_net_check_peers_wait_incoming(n, &n->peers_wait_incoming,\n+                                              errp)) {\n+        virtio_cleanup(vdev);\n+        return;\n+    }\n+\n     peer_test_vnet_hdr(n);\n     if (peer_has_vnet_hdr(n)) {\n         n->host_hdr_len = sizeof(struct virtio_net_hdr);\n@@ -4314,6 +4448,7 @@ static const Property virtio_net_properties[] = {\n                                host_features_ex,\n                                VIRTIO_NET_F_GUEST_UDP_TUNNEL_GSO_CSUM,\n                                true),\n+    DEFINE_PROP_BOOL(\"local-migration\", VirtIONet, local_migration, true),\n };\n \n static void virtio_net_class_init(ObjectClass *klass, const void *data)\ndiff --git a/include/hw/virtio/virtio-net.h b/include/hw/virtio/virtio-net.h\nindex 5b8ab7bda7..0470e47777 100644\n--- a/include/hw/virtio/virtio-net.h\n+++ b/include/hw/virtio/virtio-net.h\n@@ -231,6 +231,8 @@ struct VirtIONet {\n     struct EBPFRSSContext ebpf_rss;\n     uint32_t nr_ebpf_rss_fds;\n     char **ebpf_rss_fds;\n+    bool peers_wait_incoming;\n+    bool local_migration;\n };\n \n size_t virtio_net_handle_ctrl_iov(VirtIODevice *vdev,\ndiff --git a/include/net/net.h b/include/net/net.h\nindex aa34043b1a..d4cf399d4a 100644\n--- a/include/net/net.h\n+++ b/include/net/net.h\n@@ -82,6 +82,7 @@ typedef void (SocketReadStateFinalize)(SocketReadState *rs);\n typedef void (NetAnnounce)(NetClientState *);\n typedef bool (SetSteeringEBPF)(NetClientState *, int);\n typedef bool (NetCheckPeerType)(NetClientState *, ObjectClass *, Error **);\n+typedef bool (IsWaitIncoming)(NetClientState *);\n typedef struct vhost_net *(GetVHostNet)(NetClientState *nc);\n \n typedef struct NetClientInfo {\n@@ -110,6 +111,7 @@ typedef struct NetClientInfo {\n     NetAnnounce *announce;\n     SetSteeringEBPF *set_steering_ebpf;\n     NetCheckPeerType *check_peer_type;\n+    IsWaitIncoming *is_wait_incoming;\n     GetVHostNet *get_vhost_net;\n     const VMStateDescription *backend_vmsd;\n } NetClientInfo;\n","prefixes":["v11","5/8"]}