{"id":2225206,"url":"http://patchwork.ozlabs.org/api/1.1/patches/2225206/?format=json","web_url":"http://patchwork.ozlabs.org/project/qemu-devel/patch/20260420161958.584407-7-vsementsov@yandex-team.ru/","project":{"id":14,"url":"http://patchwork.ozlabs.org/api/1.1/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":"<20260420161958.584407-7-vsementsov@yandex-team.ru>","date":"2026-04-20T16:19:56","name":"[v14,6/8] net/tap: support local migration with virtio-net","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"3ac5e72204eb7e66203eece5fea71648111e2eba","submitter":{"id":84116,"url":"http://patchwork.ozlabs.org/api/1.1/people/84116/?format=json","name":"Vladimir Sementsov-Ogievskiy","email":"vsementsov@yandex-team.ru"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/qemu-devel/patch/20260420161958.584407-7-vsementsov@yandex-team.ru/mbox/","series":[{"id":500626,"url":"http://patchwork.ozlabs.org/api/1.1/series/500626/?format=json","web_url":"http://patchwork.ozlabs.org/project/qemu-devel/list/?series=500626","date":"2026-04-20T16:19:57","name":"virtio-net: live-TAP local migration","version":14,"mbox":"http://patchwork.ozlabs.org/series/500626/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2225206/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2225206/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=iRelpi1K;\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=lists1p.gnu.org;\n envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n receiver=patchwork.ozlabs.org)","mail-nwsmtp-smtp-corp-main-68.klg.yp-c.yandex.net;\n dkim=pass header.i=@yandex-team.ru"],"Received":["from lists1p.gnu.org (lists1p.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 4fzrM85ftwz1yJN\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 21 Apr 2026 02:20:51 +1000 (AEST)","from localhost ([::1] helo=lists1p.gnu.org)\n\tby lists1p.gnu.org with esmtp (Exim 4.90_1)\n\t(envelope-from <qemu-devel-bounces@nongnu.org>)\n\tid 1wErME-0005dr-PD; Mon, 20 Apr 2026 12:20:22 -0400","from eggs.gnu.org ([2001:470:142:3::10])\n by lists1p.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 1wErMB-0005cz-Uk\n for qemu-devel@nongnu.org; Mon, 20 Apr 2026 12:20:20 -0400","from forwardcorp1d.mail.yandex.net ([178.154.239.200])\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 1wErM8-0003HE-Rg\n for qemu-devel@nongnu.org; Mon, 20 Apr 2026 12:20:19 -0400","from mail-nwsmtp-smtp-corp-main-68.klg.yp-c.yandex.net\n (mail-nwsmtp-smtp-corp-main-68.klg.yp-c.yandex.net\n [IPv6:2a02:6b8:c42:94a9:0:640:a3fa:0])\n by forwardcorp1d.mail.yandex.net (Yandex) with ESMTPS id 7E04B8074F;\n Mon, 20 Apr 2026 19:20:15 +0300 (MSK)","from vsementsov-lin (unknown [2a02:6bf:8080:54b::1:34])\n by mail-nwsmtp-smtp-corp-main-68.klg.yp-c.yandex.net (smtpcorp) with ESMTPSA\n id 0KUTY10KuKo0-85YNqgkL; Mon, 20 Apr 2026 19:20:14 +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=1776702014;\n bh=heRfg9qbszch9V3hboL+bLT1VeUyHcLtjbcj2T2cUYM=;\n h=Message-ID:Date:In-Reply-To:Cc:Subject:References:To:From;\n b=iRelpi1KafVNZPAQwMwWZLx4YfooCm9VBNLlU0uJ1qSCiy+oZAJ8h+DElEWLRaDUL\n GP57DWcu7h12TGM3Or0PsITkkZEaL8zDdJkUYnj5C/jKw8vQIMH0zYTjbXvwOLDApd\n Vg5MSTAv3+ob8RgHxtvBRyryg2Xq6o0K+w39t4Ss=","From":"Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>","To":"jasowang@redhat.com,\n\tmst@redhat.com","Cc":"armbru@redhat.com, peterx@redhat.com, farosas@suse.de,\n raphael.s.norwitz@gmail.com, bchaney@akamai.com, vsementsov@yandex-team.ru,\n qemu-devel@nongnu.org, berrange@redhat.com, pbonzini@redhat.com,\n yc-core@yandex-team.ru, Eric Blake <eblake@redhat.com>","Subject":"[PATCH v14 6/8] net/tap: support local migration with virtio-net","Date":"Mon, 20 Apr 2026 19:19:56 +0300","Message-ID":"<20260420161958.584407-7-vsementsov@yandex-team.ru>","X-Mailer":"git-send-email 2.52.0","In-Reply-To":"<20260420161958.584407-1-vsementsov@yandex-team.ru>","References":"<20260420161958.584407-1-vsementsov@yandex-team.ru>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Received-SPF":"pass client-ip=178.154.239.200;\n envelope-from=vsementsov@yandex-team.ru; helo=forwardcorp1d.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, SPF_HELO_NONE=0.001,\n 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":"Support transferring of TAP state (including open fd) through\nmigration stream as part of viritio-net \"local-migration\".\n\nAdd new option, incoming-fds, which should be set to true to\ntrigger new logic.\n\nFor new option require explicitly unset script and downscript,\nto keep possibility of implementing support for them in future.\n\nNote disabling read polling on source stop for TAP migration:\notherwise, source process may steal packages from TAP fd even\nafter source vm STOP.\n\nSigned-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>\n---\n net/tap.c     | 147 +++++++++++++++++++++++++++++++++++++++++++++++---\n qapi/net.json |  10 +++-\n 2 files changed, 150 insertions(+), 7 deletions(-)","diff":"diff --git a/net/tap.c b/net/tap.c\nindex 9d6213fc3e5..9b1d4613a0b 100644\n--- a/net/tap.c\n+++ b/net/tap.c\n@@ -36,6 +36,7 @@\n #include \"net/net.h\"\n #include \"clients.h\"\n #include \"monitor/monitor.h\"\n+#include \"system/runstate.h\"\n #include \"system/system.h\"\n #include \"qapi/error.h\"\n #include \"qemu/cutils.h\"\n@@ -86,6 +87,9 @@ typedef struct TAPState {\n     VHostNetState *vhost_net;\n     unsigned host_vnet_hdr_len;\n     Notifier exit;\n+\n+    bool read_poll_detached;\n+    VMChangeStateEntry *vmstate;\n } TAPState;\n \n static void launch_script(const char *setup_script, const char *ifname,\n@@ -94,19 +98,25 @@ static void launch_script(const char *setup_script, const char *ifname,\n static void tap_send(void *opaque);\n static void tap_writable(void *opaque);\n \n+static bool tap_is_explicit_no_script(const char *script_arg)\n+{\n+    return script_arg &&\n+        (script_arg[0] == '\\0' || strcmp(script_arg, \"no\") == 0);\n+}\n+\n static char *tap_parse_script(const char *script_arg, const char *default_path)\n {\n     g_autofree char *res = g_strdup(script_arg);\n \n-    if (!res) {\n-        res = get_relocated_path(default_path);\n+    if (tap_is_explicit_no_script(script_arg)) {\n+        return NULL;\n     }\n \n-    if (res[0] == '\\0' || strcmp(res, \"no\") == 0) {\n-        return NULL;\n+    if (!script_arg) {\n+        return get_relocated_path(default_path);\n     }\n \n-    return g_steal_pointer(&res);\n+    return g_strdup(script_arg);\n }\n \n static void tap_update_fd_handler(TAPState *s)\n@@ -123,6 +133,23 @@ static void tap_read_poll(TAPState *s, bool enable)\n     tap_update_fd_handler(s);\n }\n \n+static void tap_vm_state_change(void *opaque, bool running, RunState state)\n+{\n+    TAPState *s = opaque;\n+\n+    if (running) {\n+        if (s->read_poll_detached) {\n+            tap_read_poll(s, true);\n+            s->read_poll_detached = false;\n+        }\n+    } else if (state == RUN_STATE_FINISH_MIGRATE) {\n+        if (s->read_poll) {\n+            s->read_poll_detached = true;\n+            tap_read_poll(s, false);\n+        }\n+    }\n+}\n+\n static void tap_write_poll(TAPState *s, bool enable)\n {\n     s->write_poll = enable;\n@@ -353,6 +380,11 @@ static void tap_cleanup(NetClientState *nc)\n         s->exit.notify = NULL;\n     }\n \n+    if (s->vmstate) {\n+        qemu_del_vm_change_state_handler(s->vmstate);\n+        s->vmstate = NULL;\n+    }\n+\n     tap_read_poll(s, false);\n     tap_write_poll(s, false);\n     close(s->fd);\n@@ -393,6 +425,65 @@ static VHostNetState *tap_get_vhost_net(NetClientState *nc)\n     return s->vhost_net;\n }\n \n+static bool tap_is_wait_incoming(NetClientState *nc)\n+{\n+    TAPState *s = DO_UPCAST(TAPState, nc, nc);\n+    assert(nc->info->type == NET_CLIENT_DRIVER_TAP);\n+    return s->fd == -1;\n+}\n+\n+static int tap_pre_load(void *opaque)\n+{\n+    TAPState *s = opaque;\n+\n+    if (s->fd != -1) {\n+        error_report(\n+            \"TAP is already initialized and cannot receive incoming fd\");\n+        return -EINVAL;\n+    }\n+\n+    return 0;\n+}\n+\n+static bool tap_setup_vhost(TAPState *s, Error **errp);\n+\n+static int tap_post_load(void *opaque, int version_id)\n+{\n+    TAPState *s = opaque;\n+    Error *local_err = NULL;\n+\n+    tap_read_poll(s, true);\n+\n+    if (s->fd < 0) {\n+        return -1;\n+    }\n+\n+    if (!tap_setup_vhost(s, &local_err)) {\n+        error_prepend(&local_err,\n+                      \"Failed to setup vhost during TAP post-load: \");\n+        error_report_err(local_err);\n+        return -1;\n+    }\n+\n+    return 0;\n+}\n+\n+static const VMStateDescription vmstate_tap = {\n+    .name = \"net-tap\",\n+    .pre_load = tap_pre_load,\n+    .post_load = tap_post_load,\n+    .fields = (const VMStateField[]) {\n+        VMSTATE_FD(fd, TAPState),\n+        VMSTATE_BOOL(using_vnet_hdr, TAPState),\n+        VMSTATE_BOOL(has_ufo, TAPState),\n+        VMSTATE_BOOL(has_uso, TAPState),\n+        VMSTATE_BOOL(has_tunnel, TAPState),\n+        VMSTATE_BOOL(enabled, TAPState),\n+        VMSTATE_UINT32(host_vnet_hdr_len, TAPState),\n+        VMSTATE_END_OF_LIST()\n+    }\n+};\n+\n /* fd support */\n \n static NetClientInfo net_tap_info = {\n@@ -412,7 +503,9 @@ static NetClientInfo net_tap_info = {\n     .set_vnet_le = tap_set_vnet_le,\n     .set_vnet_be = tap_set_vnet_be,\n     .set_steering_ebpf = tap_set_steering_ebpf,\n+    .is_wait_incoming = tap_is_wait_incoming,\n     .get_vhost_net = tap_get_vhost_net,\n+    .backend_vmsd = &vmstate_tap,\n };\n \n static TAPState *net_tap_fd_init(NetClientState *peer,\n@@ -748,6 +841,9 @@ static bool net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer,\n     int sndbuf =\n         (tap->has_sndbuf && tap->sndbuf) ? MIN(tap->sndbuf, INT_MAX) : INT_MAX;\n \n+    s->read_poll_detached = false;\n+    s->vmstate = qemu_add_vm_change_state_handler(tap_vm_state_change, s);\n+\n     if (!tap_set_sndbuf(fd, sndbuf, sndbuf_required ? errp : NULL) &&\n         sndbuf_required) {\n         goto failed;\n@@ -779,6 +875,8 @@ static bool net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer,\n     return true;\n \n failed:\n+    qemu_del_vm_change_state_handler(s->vmstate);\n+    s->vmstate = NULL;\n     qemu_del_net_client(&s->nc);\n     return false;\n }\n@@ -910,6 +1008,26 @@ int net_init_tap(const Netdev *netdev, const char *name,\n         return -1;\n     }\n \n+    if (tap->incoming_fds &&\n+        (tap->fd || tap->fds || tap->helper || tap->br || tap->ifname ||\n+         tap->has_sndbuf || tap->has_vnet_hdr)) {\n+        error_setg(errp, \"incoming-fds is incompatible with \"\n+                   \"fd=, fds=, helper=, br=, ifname=, sndbuf= and vnet_hdr=\");\n+        return -1;\n+    }\n+\n+    if (tap->incoming_fds &&\n+        !(tap_is_explicit_no_script(tap->script) &&\n+          tap_is_explicit_no_script(tap->downscript))) {\n+        /*\n+         * script=\"\" and downscript=\"\" are silently supported to be consistent\n+         * with cases without incoming_fds, but do not care to put this into\n+         * error message.\n+         */\n+        error_setg(errp, \"incoming-fds requires script=no and downscript=no\");\n+        return -1;\n+    }\n+\n     queues = tap_parse_fds_and_queues(tap, &fds, errp);\n     if (queues < 0) {\n         return -1;\n@@ -928,7 +1046,24 @@ int net_init_tap(const Netdev *netdev, const char *name,\n         goto fail;\n     }\n \n-    if (fds) {\n+    if (tap->incoming_fds) {\n+        for (i = 0; i < queues; i++) {\n+            NetClientState *nc;\n+            TAPState *s;\n+\n+            nc = qemu_new_net_client(&net_tap_info, peer, \"tap\", name);\n+            qemu_set_info_str(nc, \"incoming\");\n+\n+            s = DO_UPCAST(TAPState, nc, nc);\n+            s->fd = -1;\n+            if (vhost_fds) {\n+                s->vhostfd = vhost_fds[i];\n+                s->vhost_busyloop_timeout = tap->has_poll_us ? tap->poll_us : 0;\n+            } else {\n+                s->vhostfd = -1;\n+            }\n+        }\n+    } else if (fds) {\n         for (i = 0; i < queues; i++) {\n             if (i == 0) {\n                 vnet_hdr = tap_probe_vnet_hdr(fds[i], errp);\ndiff --git a/qapi/net.json b/qapi/net.json\nindex 118bd349651..7e3a983829a 100644\n--- a/qapi/net.json\n+++ b/qapi/net.json\n@@ -355,6 +355,13 @@\n # @poll-us: maximum number of microseconds that could be spent on busy\n #     polling for tap (since 2.7)\n #\n+# @incoming-fds: do not open or create any TAP devices.  Prepare for\n+#     getting TAP file descriptors from incoming migration stream.\n+#     The option is incompatible with any of @fd, @fds, @helper, @br,\n+#     @ifname, @sndbuf and @vnet_hdr options, and requires @script and\n+#     @downscript be explicitly set to nothing (empty string or \"no\")\n+#     (Since 11.1)\n+#\n # Since: 1.2\n ##\n { 'struct': 'NetdevTapOptions',\n@@ -373,7 +380,8 @@\n     '*vhostfds':   'str',\n     '*vhostforce': 'bool',\n     '*queues':     'uint32',\n-    '*poll-us':    'uint32'} }\n+    '*poll-us':    'uint32',\n+    '*incoming-fds': 'bool' } }\n \n ##\n # @NetdevSocketOptions:\n","prefixes":["v14","6/8"]}