qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/8] net: Add passt netdev backend
@ 2025-06-18  8:39 Laurent Vivier
  2025-06-18  8:39 ` [PATCH 1/8] net: Refactor stream logic for reuse in '-net passt' Laurent Vivier
                   ` (8 more replies)
  0 siblings, 9 replies; 13+ messages in thread
From: Laurent Vivier @ 2025-06-18  8:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Michael S. Tsirkin, Eric Blake, Stefano Garzarella,
	Stefan Weil, Jason Wang, Philippe Mathieu-Daudé,
	Marc-André Lureau, Dr. David Alan Gilbert, Markus Armbruster,
	Daniel P. Berrangé, Laurent Vivier

This series introduces support for passt as a new network backend for
QEMU.

passt is a modern, unprivileged, user-mode networking solution that
provides guest connectivity by launching an external helper process. This
series adds the core backend and integrates it with vhost-user for
high-performance, accelerated networking.

The series is structured to first improve the general networking code
before adding the new feature. The first patch extracts from the stream
backend the functions that will be reused in the passt backend. The
following patches are a preparatory refactoring to decouple the generic
vhost layer from specific backend implementations (tap, vhost-user, etc.).
This is achieved by replacing hardcoded type checks with a callback-based
system in NetClientInfo, making the vhost infrastructure more modular and
extensible.

With the refactoring in place, subsequent patches introduce the passt
backend itself, reusing the generic stream handling logic. The final
patch adds vhost-user support to passt, which plugs cleanly into the
newly refactored vhost layer.

Some benchmarks:

 Reference '-net user':

  -net user,hostfwd=tcp::10001-:10001

    iperf3 -c localhost -p 10001  -t 60 -4

    [ ID] Interval           Transfer     Bitrate         Retr
    [  5]   0.00-60.00  sec  14.2 GBytes  2.03 Gbits/sec    1            sender
    [  5]   0.00-60.00  sec  14.2 GBytes  2.03 Gbits/sec                  receiver

 New backend '-netdev passt'

  -netdev passt,vhost-user=off,tcp-ports=10001

    iperf3 -c localhost -p 10001  -t 60 -4

    [ ID] Interval           Transfer     Bitrate         Retr
    [  5]   0.00-60.00  sec  27.1 GBytes  3.88 Gbits/sec    0            sender
    [  5]   0.00-60.03  sec  27.1 GBytes  3.88 Gbits/sec                  receiver

  -netdev passt,vhost-user=on,tcp-ports=10001

    iperf3 -c localhost -p 10001  -t 60 -4

    [ ID] Interval           Transfer     Bitrate         Retr
    [  5]   0.00-60.00  sec   224 GBytes  32.1 Gbits/sec    4            sender
    [  5]   0.00-60.05  sec   224 GBytes  32.0 Gbits/sec                  receiver

Thanks,
Laurent

Laurent Vivier (8):
  net: Refactor stream logic for reuse in '-net passt'
  net: Define net_client_set_link()
  net: Introduce helper to identify vhost-user clients
  net: Add get_vhost_net callback to NetClientInfo
  net: Add get_acked_features callback to NetClientInfo
  net: Add save_acked_features callback to NetClientInfo
  net: Add passt network backend
  net/passt: Implement vhost-user backend support

 hmp-commands.hx          |   3 +
 hw/net/vhost_net-stub.c  |   1 -
 hw/net/vhost_net.c       |  89 ++---
 hw/net/virtio-net.c      |  18 +-
 include/net/net.h        |  12 +
 include/net/tap.h        |   3 -
 include/net/vhost-user.h |  19 --
 include/net/vhost-vdpa.h |   2 -
 meson.build              |   6 +
 meson_options.txt        |   2 +
 net/clients.h            |   4 +
 net/hub.c                |   3 +
 net/meson.build          |   6 +-
 net/net.c                |  55 ++-
 net/passt.c              | 718 +++++++++++++++++++++++++++++++++++++++
 net/stream.c             | 282 ++++-----------
 net/stream_data.c        | 193 +++++++++++
 net/stream_data.h        |  31 ++
 net/tap-win32.c          |   5 -
 net/tap.c                |  20 +-
 net/vhost-user-stub.c    |   1 -
 net/vhost-user.c         |  22 +-
 net/vhost-vdpa.c         |   4 +-
 qapi/net.json            | 121 +++++++
 qemu-options.hx          |  18 +
 25 files changed, 1293 insertions(+), 345 deletions(-)
 delete mode 100644 include/net/vhost-user.h
 create mode 100644 net/passt.c
 create mode 100644 net/stream_data.c
 create mode 100644 net/stream_data.h

-- 
2.49.0




^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 1/8] net: Refactor stream logic for reuse in '-net passt'
  2025-06-18  8:39 [PATCH 0/8] net: Add passt netdev backend Laurent Vivier
@ 2025-06-18  8:39 ` Laurent Vivier
  2025-06-18  8:39 ` [PATCH 2/8] net: Define net_client_set_link() Laurent Vivier
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 13+ messages in thread
From: Laurent Vivier @ 2025-06-18  8:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Michael S. Tsirkin, Eric Blake, Stefano Garzarella,
	Stefan Weil, Jason Wang, Philippe Mathieu-Daudé,
	Marc-André Lureau, Dr. David Alan Gilbert, Markus Armbruster,
	Daniel P. Berrangé, Laurent Vivier

To prepare for the implementation of '-net passt', this patch moves
the generic stream handling functions from net/stream.c into new
net/stream_data.c and net/stream_data.h files.

This refactoring introduces a NetStreamData struct that encapsulates
the generic fields and logic previously in NetStreamState. The
NetStreamState now embeds NetStreamData and delegates the core
stream operations to the new generic functions.

To maintain flexibility for different users of this generic code,
callbacks for send and listen operations are now passed via
function pointers within the NetStreamData struct. This allows
callers to provide their own specific implementations while reusing
the common connection and data transfer logic.

Signed-off-by: Laurent Vivier <lvivier@redhat.com>
---
 net/meson.build   |   3 +-
 net/stream.c      | 282 +++++++++++-----------------------------------
 net/stream_data.c | 193 +++++++++++++++++++++++++++++++
 net/stream_data.h |  31 +++++
 4 files changed, 290 insertions(+), 219 deletions(-)
 create mode 100644 net/stream_data.c
 create mode 100644 net/stream_data.h

diff --git a/net/meson.build b/net/meson.build
index bb97b4dcbeb6..bb3c011e5a30 100644
--- a/net/meson.build
+++ b/net/meson.build
@@ -1,6 +1,7 @@
 system_ss.add(files(
   'announce.c',
   'checksum.c',
+  'dgram.c',
   'dump.c',
   'eth.c',
   'filter-buffer.c',
@@ -12,7 +13,7 @@ system_ss.add(files(
   'queue.c',
   'socket.c',
   'stream.c',
-  'dgram.c',
+  'stream_data.c',
   'util.c',
 ))
 
diff --git a/net/stream.c b/net/stream.c
index 4de56138445c..d893f02cabe3 100644
--- a/net/stream.c
+++ b/net/stream.c
@@ -27,173 +27,50 @@
 
 #include "net/net.h"
 #include "clients.h"
-#include "monitor/monitor.h"
 #include "qapi/error.h"
-#include "qemu/error-report.h"
-#include "qemu/option.h"
-#include "qemu/sockets.h"
-#include "qemu/iov.h"
-#include "qemu/main-loop.h"
-#include "qemu/cutils.h"
-#include "io/channel.h"
-#include "io/channel-socket.h"
 #include "io/net-listener.h"
 #include "qapi/qapi-events-net.h"
 #include "qapi/qapi-visit-sockets.h"
 #include "qapi/clone-visitor.h"
 
+#include "stream_data.h"
+
 typedef struct NetStreamState {
-    NetClientState nc;
-    QIOChannel *listen_ioc;
-    QIONetListener *listener;
-    QIOChannel *ioc;
-    guint ioc_read_tag;
-    guint ioc_write_tag;
-    SocketReadState rs;
-    unsigned int send_index;      /* number of bytes sent*/
+    NetStreamData data;
     uint32_t reconnect_ms;
     guint timer_tag;
     SocketAddress *addr;
 } NetStreamState;
 
-static void net_stream_listen(QIONetListener *listener,
-                              QIOChannelSocket *cioc,
-                              void *opaque);
 static void net_stream_arm_reconnect(NetStreamState *s);
 
-static gboolean net_stream_writable(QIOChannel *ioc,
-                                    GIOCondition condition,
-                                    gpointer data)
-{
-    NetStreamState *s = data;
-
-    s->ioc_write_tag = 0;
-
-    qemu_flush_queued_packets(&s->nc);
-
-    return G_SOURCE_REMOVE;
-}
-
 static ssize_t net_stream_receive(NetClientState *nc, const uint8_t *buf,
                                   size_t size)
 {
-    NetStreamState *s = DO_UPCAST(NetStreamState, nc, nc);
-    uint32_t len = htonl(size);
-    struct iovec iov[] = {
-        {
-            .iov_base = &len,
-            .iov_len  = sizeof(len),
-        }, {
-            .iov_base = (void *)buf,
-            .iov_len  = size,
-        },
-    };
-    struct iovec local_iov[2];
-    unsigned int nlocal_iov;
-    size_t remaining;
-    ssize_t ret;
-
-    remaining = iov_size(iov, 2) - s->send_index;
-    nlocal_iov = iov_copy(local_iov, 2, iov, 2, s->send_index, remaining);
-    ret = qio_channel_writev(s->ioc, local_iov, nlocal_iov, NULL);
-    if (ret == QIO_CHANNEL_ERR_BLOCK) {
-        ret = 0; /* handled further down */
-    }
-    if (ret == -1) {
-        s->send_index = 0;
-        return -errno;
-    }
-    if (ret < (ssize_t)remaining) {
-        s->send_index += ret;
-        s->ioc_write_tag = qio_channel_add_watch(s->ioc, G_IO_OUT,
-                                                 net_stream_writable, s, NULL);
-        return 0;
-    }
-    s->send_index = 0;
-    return size;
-}
-
-static gboolean net_stream_send(QIOChannel *ioc,
-                                GIOCondition condition,
-                                gpointer data);
-
-static void net_stream_send_completed(NetClientState *nc, ssize_t len)
-{
-    NetStreamState *s = DO_UPCAST(NetStreamState, nc, nc);
-
-    if (!s->ioc_read_tag) {
-        s->ioc_read_tag = qio_channel_add_watch(s->ioc, G_IO_IN,
-                                                net_stream_send, s, NULL);
-    }
-}
+    NetStreamData *d = DO_UPCAST(NetStreamData, nc, nc);
 
-static void net_stream_rs_finalize(SocketReadState *rs)
-{
-    NetStreamState *s = container_of(rs, NetStreamState, rs);
-
-    if (qemu_send_packet_async(&s->nc, rs->buf,
-                               rs->packet_len,
-                               net_stream_send_completed) == 0) {
-        if (s->ioc_read_tag) {
-            g_source_remove(s->ioc_read_tag);
-            s->ioc_read_tag = 0;
-        }
-    }
+    return net_stream_data_receive(d, buf, size);
 }
 
 static gboolean net_stream_send(QIOChannel *ioc,
                                 GIOCondition condition,
                                 gpointer data)
 {
-    NetStreamState *s = data;
-    int size;
-    int ret;
-    char buf1[NET_BUFSIZE];
-    const char *buf;
-
-    size = qio_channel_read(s->ioc, buf1, sizeof(buf1), NULL);
-    if (size < 0) {
-        if (errno != EWOULDBLOCK) {
-            goto eoc;
-        }
-    } else if (size == 0) {
-        /* end of connection */
-    eoc:
-        s->ioc_read_tag = 0;
-        if (s->ioc_write_tag) {
-            g_source_remove(s->ioc_write_tag);
-            s->ioc_write_tag = 0;
-        }
-        if (s->listener) {
-            qemu_set_info_str(&s->nc, "listening");
-            qio_net_listener_set_client_func(s->listener, net_stream_listen,
-                                             s, NULL);
-        }
-        object_unref(OBJECT(s->ioc));
-        s->ioc = NULL;
-
-        net_socket_rs_init(&s->rs, net_stream_rs_finalize, false);
-        s->nc.link_down = true;
+    if (net_stream_data_send(ioc, condition, data) == G_SOURCE_REMOVE) {
+        NetStreamState *s = DO_UPCAST(NetStreamState, data, data);
 
-        qapi_event_send_netdev_stream_disconnected(s->nc.name);
+        qapi_event_send_netdev_stream_disconnected(s->data.nc.name);
         net_stream_arm_reconnect(s);
 
         return G_SOURCE_REMOVE;
     }
-    buf = buf1;
-
-    ret = net_fill_rstate(&s->rs, (const uint8_t *)buf, size);
-
-    if (ret == -1) {
-        goto eoc;
-    }
 
     return G_SOURCE_CONTINUE;
 }
 
 static void net_stream_cleanup(NetClientState *nc)
 {
-    NetStreamState *s = DO_UPCAST(NetStreamState, nc, nc);
+    NetStreamState *s = DO_UPCAST(NetStreamState, data.nc, nc);
     if (s->timer_tag) {
         g_source_remove(s->timer_tag);
         s->timer_tag = 0;
@@ -202,28 +79,28 @@ static void net_stream_cleanup(NetClientState *nc)
         qapi_free_SocketAddress(s->addr);
         s->addr = NULL;
     }
-    if (s->ioc) {
-        if (QIO_CHANNEL_SOCKET(s->ioc)->fd != -1) {
-            if (s->ioc_read_tag) {
-                g_source_remove(s->ioc_read_tag);
-                s->ioc_read_tag = 0;
+    if (s->data.ioc) {
+        if (QIO_CHANNEL_SOCKET(s->data.ioc)->fd != -1) {
+            if (s->data.ioc_read_tag) {
+                g_source_remove(s->data.ioc_read_tag);
+                s->data.ioc_read_tag = 0;
             }
-            if (s->ioc_write_tag) {
-                g_source_remove(s->ioc_write_tag);
-                s->ioc_write_tag = 0;
+            if (s->data.ioc_write_tag) {
+                g_source_remove(s->data.ioc_write_tag);
+                s->data.ioc_write_tag = 0;
             }
         }
-        object_unref(OBJECT(s->ioc));
-        s->ioc = NULL;
+        object_unref(OBJECT(s->data.ioc));
+        s->data.ioc = NULL;
     }
-    if (s->listen_ioc) {
-        if (s->listener) {
-            qio_net_listener_disconnect(s->listener);
-            object_unref(OBJECT(s->listener));
-            s->listener = NULL;
+    if (s->data.listen_ioc) {
+        if (s->data.listener) {
+            qio_net_listener_disconnect(s->data.listener);
+            object_unref(OBJECT(s->data.listener));
+            s->data.listener = NULL;
         }
-        object_unref(OBJECT(s->listen_ioc));
-        s->listen_ioc = NULL;
+        object_unref(OBJECT(s->data.listen_ioc));
+        s->data.listen_ioc = NULL;
     }
 }
 
@@ -235,23 +112,13 @@ static NetClientInfo net_stream_info = {
 };
 
 static void net_stream_listen(QIONetListener *listener,
-                              QIOChannelSocket *cioc,
-                              void *opaque)
+                                  QIOChannelSocket *cioc, gpointer data)
 {
-    NetStreamState *s = opaque;
+    NetStreamData *d = data;
     SocketAddress *addr;
     char *uri;
 
-    object_ref(OBJECT(cioc));
-
-    qio_net_listener_set_client_func(s->listener, NULL, s, NULL);
-
-    s->ioc = QIO_CHANNEL(cioc);
-    qio_channel_set_name(s->ioc, "stream-server");
-    s->nc.link_down = false;
-
-    s->ioc_read_tag = qio_channel_add_watch(s->ioc, G_IO_IN, net_stream_send,
-                                            s, NULL);
+    net_stream_data_listen(listener, cioc, data);
 
     if (cioc->localAddr.ss_family == AF_UNIX) {
         addr = qio_channel_socket_get_local_address(cioc, NULL);
@@ -260,22 +127,22 @@ static void net_stream_listen(QIONetListener *listener,
     }
     g_assert(addr != NULL);
     uri = socket_uri(addr);
-    qemu_set_info_str(&s->nc, "%s", uri);
+    qemu_set_info_str(&d->nc, "%s", uri);
     g_free(uri);
-    qapi_event_send_netdev_stream_connected(s->nc.name, addr);
+    qapi_event_send_netdev_stream_connected(d->nc.name, addr);
     qapi_free_SocketAddress(addr);
 }
 
 static void net_stream_server_listening(QIOTask *task, gpointer opaque)
 {
-    NetStreamState *s = opaque;
-    QIOChannelSocket *listen_sioc = QIO_CHANNEL_SOCKET(s->listen_ioc);
+    NetStreamData *d = opaque;
+    QIOChannelSocket *listen_sioc = QIO_CHANNEL_SOCKET(d->listen_ioc);
     SocketAddress *addr;
     int ret;
     Error *err = NULL;
 
     if (qio_task_propagate_error(task, &err)) {
-        qemu_set_info_str(&s->nc, "error: %s", error_get_pretty(err));
+        qemu_set_info_str(&d->nc, "error: %s", error_get_pretty(err));
         error_free(err);
         return;
     }
@@ -284,20 +151,21 @@ static void net_stream_server_listening(QIOTask *task, gpointer opaque)
     g_assert(addr != NULL);
     ret = qemu_socket_try_set_nonblock(listen_sioc->fd);
     if (addr->type == SOCKET_ADDRESS_TYPE_FD && ret < 0) {
-        qemu_set_info_str(&s->nc, "can't use file descriptor %s (errno %d)",
+        qemu_set_info_str(&d->nc, "can't use file descriptor %s (errno %d)",
                           addr->u.fd.str, -ret);
         return;
     }
     g_assert(ret == 0);
     qapi_free_SocketAddress(addr);
 
-    s->nc.link_down = true;
-    s->listener = qio_net_listener_new();
+    d->nc.link_down = true;
+    d->listener = qio_net_listener_new();
 
-    qemu_set_info_str(&s->nc, "listening");
-    net_socket_rs_init(&s->rs, net_stream_rs_finalize, false);
-    qio_net_listener_set_client_func(s->listener, net_stream_listen, s, NULL);
-    qio_net_listener_add(s->listener, listen_sioc);
+    qemu_set_info_str(&d->nc, "listening");
+    net_socket_rs_init(&d->rs, net_stream_data_rs_finalize, false);
+    qio_net_listener_set_client_func(d->listener, d->listen, d,
+                                     NULL);
+    qio_net_listener_add(d->listener, listen_sioc);
 }
 
 static int net_stream_server_init(NetClientState *peer,
@@ -307,16 +175,18 @@ static int net_stream_server_init(NetClientState *peer,
                                   Error **errp)
 {
     NetClientState *nc;
-    NetStreamState *s;
+    NetStreamData *d;
     QIOChannelSocket *listen_sioc = qio_channel_socket_new();
 
     nc = qemu_new_net_client(&net_stream_info, peer, model, name);
-    s = DO_UPCAST(NetStreamState, nc, nc);
-    qemu_set_info_str(&s->nc, "initializing");
+    d = DO_UPCAST(NetStreamData, nc, nc);
+    d->send = net_stream_send;
+    d->listen = net_stream_listen;
+    qemu_set_info_str(&d->nc, "initializing");
 
-    s->listen_ioc = QIO_CHANNEL(listen_sioc);
+    d->listen_ioc = QIO_CHANNEL(listen_sioc);
     qio_channel_socket_listen_async(listen_sioc, addr, 0,
-                                    net_stream_server_listening, s,
+                                    net_stream_server_listening, d,
                                     NULL, NULL);
 
     return 0;
@@ -325,49 +195,23 @@ static int net_stream_server_init(NetClientState *peer,
 static void net_stream_client_connected(QIOTask *task, gpointer opaque)
 {
     NetStreamState *s = opaque;
-    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(s->ioc);
+    NetStreamData *d = &s->data;
+    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(d->ioc);
     SocketAddress *addr;
     gchar *uri;
-    int ret;
-    Error *err = NULL;
 
-    if (qio_task_propagate_error(task, &err)) {
-        qemu_set_info_str(&s->nc, "error: %s", error_get_pretty(err));
-        error_free(err);
-        goto error;
+    if (net_stream_data_client_connected(task, d) == -1) {
+        net_stream_arm_reconnect(s);
+        return;
     }
 
     addr = qio_channel_socket_get_remote_address(sioc, NULL);
     g_assert(addr != NULL);
     uri = socket_uri(addr);
-    qemu_set_info_str(&s->nc, "%s", uri);
+    qemu_set_info_str(&d->nc, "%s", uri);
     g_free(uri);
-
-    ret = qemu_socket_try_set_nonblock(sioc->fd);
-    if (addr->type == SOCKET_ADDRESS_TYPE_FD && ret < 0) {
-        qemu_set_info_str(&s->nc, "can't use file descriptor %s (errno %d)",
-                          addr->u.fd.str, -ret);
-        qapi_free_SocketAddress(addr);
-        goto error;
-    }
-    g_assert(ret == 0);
-
-    net_socket_rs_init(&s->rs, net_stream_rs_finalize, false);
-
-    /* Disable Nagle algorithm on TCP sockets to reduce latency */
-    qio_channel_set_delay(s->ioc, false);
-
-    s->ioc_read_tag = qio_channel_add_watch(s->ioc, G_IO_IN, net_stream_send,
-                                            s, NULL);
-    s->nc.link_down = false;
-    qapi_event_send_netdev_stream_connected(s->nc.name, addr);
+    qapi_event_send_netdev_stream_connected(d->nc.name, addr);
     qapi_free_SocketAddress(addr);
-
-    return;
-error:
-    object_unref(OBJECT(s->ioc));
-    s->ioc = NULL;
-    net_stream_arm_reconnect(s);
 }
 
 static gboolean net_stream_reconnect(gpointer data)
@@ -378,7 +222,7 @@ static gboolean net_stream_reconnect(gpointer data)
     s->timer_tag = 0;
 
     sioc = qio_channel_socket_new();
-    s->ioc = QIO_CHANNEL(sioc);
+    s->data.ioc = QIO_CHANNEL(sioc);
     qio_channel_socket_connect_async(sioc, s->addr,
                                      net_stream_client_connected, s,
                                      NULL, NULL);
@@ -388,7 +232,7 @@ static gboolean net_stream_reconnect(gpointer data)
 static void net_stream_arm_reconnect(NetStreamState *s)
 {
     if (s->reconnect_ms && s->timer_tag == 0) {
-        qemu_set_info_str(&s->nc, "connecting");
+        qemu_set_info_str(&s->data.nc, "connecting");
         s->timer_tag = g_timeout_add(s->reconnect_ms, net_stream_reconnect, s);
     }
 }
@@ -405,11 +249,13 @@ static int net_stream_client_init(NetClientState *peer,
     QIOChannelSocket *sioc = qio_channel_socket_new();
 
     nc = qemu_new_net_client(&net_stream_info, peer, model, name);
-    s = DO_UPCAST(NetStreamState, nc, nc);
-    qemu_set_info_str(&s->nc, "connecting");
+    s = DO_UPCAST(NetStreamState, data.nc, nc);
+    qemu_set_info_str(&s->data.nc, "connecting");
 
-    s->ioc = QIO_CHANNEL(sioc);
-    s->nc.link_down = true;
+    s->data.ioc = QIO_CHANNEL(sioc);
+    s->data.nc.link_down = true;
+    s->data.send = net_stream_send;
+    s->data.listen = net_stream_listen;
 
     s->reconnect_ms = reconnect_ms;
     if (reconnect_ms) {
diff --git a/net/stream_data.c b/net/stream_data.c
new file mode 100644
index 000000000000..bb52bef9d5aa
--- /dev/null
+++ b/net/stream_data.c
@@ -0,0 +1,193 @@
+/*
+ * net stream generic functions
+ *
+ * Copyright Red Hat
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/iov.h"
+#include "qapi/error.h"
+#include "net/net.h"
+#include "io/channel.h"
+#include "io/net-listener.h"
+
+#include "stream_data.h"
+
+static gboolean net_stream_data_writable(QIOChannel *ioc,
+                                         GIOCondition condition, gpointer data)
+{
+    NetStreamData *d = data;
+
+    d->ioc_write_tag = 0;
+
+    qemu_flush_queued_packets(&d->nc);
+
+    return G_SOURCE_REMOVE;
+}
+
+ssize_t net_stream_data_receive(NetStreamData *d, const uint8_t *buf,
+                                size_t size)
+{
+    uint32_t len = htonl(size);
+    struct iovec iov[] = {
+        {
+            .iov_base = &len,
+            .iov_len  = sizeof(len),
+        }, {
+            .iov_base = (void *)buf,
+            .iov_len  = size,
+        },
+    };
+    struct iovec local_iov[2];
+    unsigned int nlocal_iov;
+    size_t remaining;
+    ssize_t ret;
+
+    remaining = iov_size(iov, 2) - d->send_index;
+    nlocal_iov = iov_copy(local_iov, 2, iov, 2, d->send_index, remaining);
+    ret = qio_channel_writev(d->ioc, local_iov, nlocal_iov, NULL);
+    if (ret == QIO_CHANNEL_ERR_BLOCK) {
+        ret = 0; /* handled further down */
+    }
+    if (ret == -1) {
+        d->send_index = 0;
+        return -errno;
+    }
+    if (ret < (ssize_t)remaining) {
+        d->send_index += ret;
+        d->ioc_write_tag = qio_channel_add_watch(d->ioc, G_IO_OUT,
+                                                 net_stream_data_writable, d,
+                                                 NULL);
+        return 0;
+    }
+    d->send_index = 0;
+    return size;
+}
+
+static void net_stream_data_send_completed(NetClientState *nc, ssize_t len)
+{
+    NetStreamData *d = DO_UPCAST(NetStreamData, nc, nc);
+
+    if (!d->ioc_read_tag) {
+        d->ioc_read_tag = qio_channel_add_watch(d->ioc, G_IO_IN, d->send, d,
+                                                NULL);
+    }
+}
+
+void net_stream_data_rs_finalize(SocketReadState *rs)
+{
+    NetStreamData *d = container_of(rs, NetStreamData, rs);
+
+    if (qemu_send_packet_async(&d->nc, rs->buf,
+                               rs->packet_len,
+                               net_stream_data_send_completed) == 0) {
+        if (d->ioc_read_tag) {
+            g_source_remove(d->ioc_read_tag);
+            d->ioc_read_tag = 0;
+        }
+    }
+}
+
+gboolean net_stream_data_send(QIOChannel *ioc, GIOCondition condition,
+                              NetStreamData *d)
+{
+    int size;
+    int ret;
+    char buf1[NET_BUFSIZE];
+    const char *buf;
+
+    size = qio_channel_read(d->ioc, buf1, sizeof(buf1), NULL);
+    if (size < 0) {
+        if (errno != EWOULDBLOCK) {
+            goto eoc;
+        }
+    } else if (size == 0) {
+        /* end of connection */
+    eoc:
+        d->ioc_read_tag = 0;
+        if (d->ioc_write_tag) {
+            g_source_remove(d->ioc_write_tag);
+            d->ioc_write_tag = 0;
+        }
+        if (d->listener) {
+            qemu_set_info_str(&d->nc, "listening");
+            qio_net_listener_set_client_func(d->listener,
+                                             d->listen, d, NULL);
+        }
+        object_unref(OBJECT(d->ioc));
+        d->ioc = NULL;
+
+        net_socket_rs_init(&d->rs, net_stream_data_rs_finalize, false);
+        d->nc.link_down = true;
+
+        return G_SOURCE_REMOVE;
+    }
+    buf = buf1;
+
+    ret = net_fill_rstate(&d->rs, (const uint8_t *)buf, size);
+
+    if (ret == -1) {
+        goto eoc;
+    }
+
+    return G_SOURCE_CONTINUE;
+}
+
+void net_stream_data_listen(QIONetListener *listener,
+                            QIOChannelSocket *cioc,
+                            NetStreamData *d)
+{
+    object_ref(OBJECT(cioc));
+
+    qio_net_listener_set_client_func(d->listener, NULL, d, NULL);
+
+    d->ioc = QIO_CHANNEL(cioc);
+    qio_channel_set_name(d->ioc, "stream-server");
+    d->nc.link_down = false;
+
+    d->ioc_read_tag = qio_channel_add_watch(d->ioc, G_IO_IN, d->send, d, NULL);
+}
+
+int net_stream_data_client_connected(QIOTask *task, NetStreamData *d)
+{
+    QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(d->ioc);
+    SocketAddress *addr;
+    int ret;
+    Error *err = NULL;
+
+    if (qio_task_propagate_error(task, &err)) {
+        qemu_set_info_str(&d->nc, "error: %s", error_get_pretty(err));
+        error_free(err);
+        goto error;
+    }
+
+    addr = qio_channel_socket_get_remote_address(sioc, NULL);
+    g_assert(addr != NULL);
+
+    ret = qemu_socket_try_set_nonblock(sioc->fd);
+    if (addr->type == SOCKET_ADDRESS_TYPE_FD && ret < 0) {
+        qemu_set_info_str(&d->nc, "can't use file descriptor %s (errno %d)",
+                          addr->u.fd.str, -ret);
+        qapi_free_SocketAddress(addr);
+        goto error;
+    }
+    g_assert(ret == 0);
+    qapi_free_SocketAddress(addr);
+
+    net_socket_rs_init(&d->rs, net_stream_data_rs_finalize, false);
+
+    /* Disable Nagle algorithm on TCP sockets to reduce latency */
+    qio_channel_set_delay(d->ioc, false);
+
+    d->ioc_read_tag = qio_channel_add_watch(d->ioc, G_IO_IN, d->send, d, NULL);
+    d->nc.link_down = false;
+
+    return 0;
+error:
+    object_unref(OBJECT(d->ioc));
+    d->ioc = NULL;
+
+    return -1;
+}
diff --git a/net/stream_data.h b/net/stream_data.h
new file mode 100644
index 000000000000..b868625665ae
--- /dev/null
+++ b/net/stream_data.h
@@ -0,0 +1,31 @@
+/*
+ * net stream generic functions
+ *
+ * Copyright Red Hat
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+typedef struct NetStreamData {
+    NetClientState nc;
+    QIOChannel *ioc;
+    guint ioc_read_tag;
+    guint ioc_write_tag;
+    SocketReadState rs;
+    unsigned int send_index;      /* number of bytes sent*/
+    QIOChannelFunc send;
+    /* server data */
+    QIOChannel *listen_ioc;
+    QIONetListener *listener;
+    QIONetListenerClientFunc listen;
+} NetStreamData;
+
+ssize_t net_stream_data_receive(NetStreamData *d, const uint8_t *buf,
+                                size_t size);
+void net_stream_data_rs_finalize(SocketReadState *rs);
+gboolean net_stream_data_send(QIOChannel *ioc, GIOCondition condition,
+                              NetStreamData *d);
+int net_stream_data_client_connected(QIOTask *task, NetStreamData *d);
+void net_stream_data_listen(QIONetListener *listener,
+                            QIOChannelSocket *cioc,
+                            NetStreamData *d);
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 2/8] net: Define net_client_set_link()
  2025-06-18  8:39 [PATCH 0/8] net: Add passt netdev backend Laurent Vivier
  2025-06-18  8:39 ` [PATCH 1/8] net: Refactor stream logic for reuse in '-net passt' Laurent Vivier
@ 2025-06-18  8:39 ` Laurent Vivier
  2025-06-18  8:39 ` [PATCH 3/8] net: Introduce helper to identify vhost-user clients Laurent Vivier
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 13+ messages in thread
From: Laurent Vivier @ 2025-06-18  8:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Michael S. Tsirkin, Eric Blake, Stefano Garzarella,
	Stefan Weil, Jason Wang, Philippe Mathieu-Daudé,
	Marc-André Lureau, Dr. David Alan Gilbert, Markus Armbruster,
	Daniel P. Berrangé, Laurent Vivier

The code to set the link status is currently located in
qmp_set_link(). This function identifies the device by name,
searches for the corresponding NetClientState, and then updates
the link status.

In some parts of the code, such as vhost-user.c, the
NetClientState are already available. Calling qmp_set_link()
from these locations leads to a redundant search for the clients.

This patch refactors the logic by introducing a new function,
net_client_set_link(), which accepts a NetClientState array
directly. qmp_set_link() is simplified to be a wrapper that
performs the client search and then calls the new function.
The vhost-user implementation is updated to use net_client_set_link()
directly, thereby eliminating the unnecessary client lookup.

Signed-off-by: Laurent Vivier <lvivier@redhat.com>
---
 include/net/net.h |  1 +
 net/net.c         | 32 ++++++++++++++++++++------------
 net/vhost-user.c  |  4 ++--
 3 files changed, 23 insertions(+), 14 deletions(-)

diff --git a/include/net/net.h b/include/net/net.h
index cdd5b109b0d2..ac59b593ba48 100644
--- a/include/net/net.h
+++ b/include/net/net.h
@@ -298,6 +298,7 @@ void net_client_parse(QemuOptsList *opts_list, const char *optstr);
 void show_netdevs(void);
 void net_init_clients(void);
 void net_check_clients(void);
+void net_client_set_link(NetClientState **ncs, int queues, bool up);
 void net_cleanup(void);
 void hmp_host_net_add(Monitor *mon, const QDict *qdict);
 void hmp_host_net_remove(Monitor *mon, const QDict *qdict);
diff --git a/net/net.c b/net/net.c
index 39d6f28158a3..cfa2d8e95827 100644
--- a/net/net.c
+++ b/net/net.c
@@ -1601,21 +1601,11 @@ void colo_notify_filters_event(int event, Error **errp)
     }
 }
 
-void qmp_set_link(const char *name, bool up, Error **errp)
+void net_client_set_link(NetClientState **ncs, int queues, bool up)
 {
-    NetClientState *ncs[MAX_QUEUE_NUM];
     NetClientState *nc;
-    int queues, i;
-
-    queues = qemu_find_net_clients_except(name, ncs,
-                                          NET_CLIENT_DRIVER__MAX,
-                                          MAX_QUEUE_NUM);
+    int i;
 
-    if (queues == 0) {
-        error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
-                  "Device '%s' not found", name);
-        return;
-    }
     nc = ncs[0];
 
     for (i = 0; i < queues; i++) {
@@ -1646,6 +1636,24 @@ void qmp_set_link(const char *name, bool up, Error **errp)
     }
 }
 
+void qmp_set_link(const char *name, bool up, Error **errp)
+{
+    NetClientState *ncs[MAX_QUEUE_NUM];
+    int queues;
+
+    queues = qemu_find_net_clients_except(name, ncs,
+                                          NET_CLIENT_DRIVER__MAX,
+                                          MAX_QUEUE_NUM);
+
+    if (queues == 0) {
+        error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
+                  "Device '%s' not found", name);
+        return;
+    }
+
+    net_client_set_link(ncs, queues, up);
+}
+
 static void net_vm_change_state_handler(void *opaque, bool running,
                                         RunState state)
 {
diff --git a/net/vhost-user.c b/net/vhost-user.c
index 0b235e50c650..10ac8dc0b3d7 100644
--- a/net/vhost-user.c
+++ b/net/vhost-user.c
@@ -264,7 +264,7 @@ static void chr_closed_bh(void *opaque)
         vhost_user_save_acked_features(ncs[i]);
     }
 
-    qmp_set_link(name, false, &err);
+    net_client_set_link(ncs, queues, false);
 
     qemu_chr_fe_set_handlers(&s->chr, NULL, NULL, net_vhost_user_event,
                              NULL, opaque, NULL, true);
@@ -300,7 +300,7 @@ static void net_vhost_user_event(void *opaque, QEMUChrEvent event)
         }
         s->watch = qemu_chr_fe_add_watch(&s->chr, G_IO_HUP,
                                          net_vhost_user_watch, s);
-        qmp_set_link(name, true, &err);
+        net_client_set_link(ncs, queues, true);
         s->started = true;
         qapi_event_send_netdev_vhost_user_connected(name, chr->label);
         break;
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 3/8] net: Introduce helper to identify vhost-user clients
  2025-06-18  8:39 [PATCH 0/8] net: Add passt netdev backend Laurent Vivier
  2025-06-18  8:39 ` [PATCH 1/8] net: Refactor stream logic for reuse in '-net passt' Laurent Vivier
  2025-06-18  8:39 ` [PATCH 2/8] net: Define net_client_set_link() Laurent Vivier
@ 2025-06-18  8:39 ` Laurent Vivier
  2025-06-18  8:39 ` [PATCH 4/8] net: Add get_vhost_net callback to NetClientInfo Laurent Vivier
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 13+ messages in thread
From: Laurent Vivier @ 2025-06-18  8:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Michael S. Tsirkin, Eric Blake, Stefano Garzarella,
	Stefan Weil, Jason Wang, Philippe Mathieu-Daudé,
	Marc-André Lureau, Dr. David Alan Gilbert, Markus Armbruster,
	Daniel P. Berrangé, Laurent Vivier

Currently, several parts of the codebase check if a network client is a
vhost-user backend by directly comparing its type against the
NET_CLIENT_DRIVER_VHOST_USER enum. This creates a tight coupling
between virtio/vhost-net drivers and the internal implementation
details of the vhost-user net client.

To improve abstraction and reduce this coupling, this patch introduces a
new helper function, qemu_is_vhost_user(). This function allows
callers to query if a client is of the vhost-user type without
needing to know about the specific enum value.

The mechanism uses a new is_vhost_user function pointer in the
NetClientInfo structure, which is implemented by the vhost-user
backend.

All existing direct checks are replaced with calls to the new helper.
This simplifies the logic in vhost_net.c and virtio-net.c,
making the code cleaner and more maintainable.

Signed-off-by: Laurent Vivier <lvivier@redhat.com>
---
 hw/net/vhost_net.c  | 33 +++++++++++++++------------------
 hw/net/virtio-net.c | 18 ++++++++++--------
 include/net/net.h   |  3 +++
 net/net.c           |  9 +++++++++
 net/vhost-user.c    |  8 ++++++++
 5 files changed, 45 insertions(+), 26 deletions(-)

diff --git a/hw/net/vhost_net.c b/hw/net/vhost_net.c
index 891f235a0a6e..41d21e687ed9 100644
--- a/hw/net/vhost_net.c
+++ b/hw/net/vhost_net.c
@@ -98,27 +98,24 @@ static const int user_feature_bits[] = {
 
 static const int *vhost_net_get_feature_bits(struct vhost_net *net)
 {
-    const int *feature_bits = 0;
+    if (net->nc->info->type == NET_CLIENT_DRIVER_TAP) {
+        return kernel_feature_bits;
+    }
+
+    if (qemu_is_vhost_user(net->nc)) {
+        return user_feature_bits;
+    }
 
-    switch (net->nc->info->type) {
-    case NET_CLIENT_DRIVER_TAP:
-        feature_bits = kernel_feature_bits;
-        break;
-    case NET_CLIENT_DRIVER_VHOST_USER:
-        feature_bits = user_feature_bits;
-        break;
 #ifdef CONFIG_VHOST_NET_VDPA
-    case NET_CLIENT_DRIVER_VHOST_VDPA:
-        feature_bits = vdpa_feature_bits;
-        break;
-#endif
-    default:
-        error_report("Feature bits not defined for this type: %d",
-                net->nc->info->type);
-        break;
+    if (net->nc->info->type == NET_CLIENT_DRIVER_VHOST_VDPA) {
+        return vdpa_feature_bits;
     }
+#endif
 
-    return feature_bits;
+    error_report("Feature bits not defined for this type: %d",
+                 net->nc->info->type);
+
+    return 0;
 }
 
 uint64_t vhost_net_get_features(struct vhost_net *net, uint64_t features)
@@ -525,7 +522,7 @@ int vhost_net_start(VirtIODevice *dev, NetClientState *ncs,
          * because vhost user doesn't interrupt masking/unmasking
          * properly.
          */
-        if (net->nc->info->type == NET_CLIENT_DRIVER_VHOST_USER) {
+        if (qemu_is_vhost_user(net->nc)) {
             dev->use_guest_notifier_mask = false;
         }
      }
diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c
index 221252e00a50..abb5e5a9860d 100644
--- a/hw/net/virtio-net.c
+++ b/hw/net/virtio-net.c
@@ -679,13 +679,15 @@ static int virtio_net_max_tx_queue_size(VirtIONet *n)
         return VIRTIO_NET_TX_QUEUE_DEFAULT_SIZE;
     }
 
-    switch(peer->info->type) {
-    case NET_CLIENT_DRIVER_VHOST_USER:
-    case NET_CLIENT_DRIVER_VHOST_VDPA:
+    if (qemu_is_vhost_user(peer)) {
         return VIRTQUEUE_MAX_SIZE;
-    default:
-        return VIRTIO_NET_TX_QUEUE_DEFAULT_SIZE;
-    };
+    }
+
+    if (peer->info->type == NET_CLIENT_DRIVER_VHOST_VDPA) {
+        return VIRTQUEUE_MAX_SIZE;
+    }
+
+    return VIRTIO_NET_TX_QUEUE_DEFAULT_SIZE;
 }
 
 static int peer_attach(VirtIONet *n, int index)
@@ -696,7 +698,7 @@ static int peer_attach(VirtIONet *n, int index)
         return 0;
     }
 
-    if (nc->peer->info->type == NET_CLIENT_DRIVER_VHOST_USER) {
+    if (qemu_is_vhost_user(nc->peer)) {
         vhost_set_vring_enable(nc->peer, 1);
     }
 
@@ -719,7 +721,7 @@ static int peer_detach(VirtIONet *n, int index)
         return 0;
     }
 
-    if (nc->peer->info->type == NET_CLIENT_DRIVER_VHOST_USER) {
+    if (qemu_is_vhost_user(nc->peer)) {
         vhost_set_vring_enable(nc->peer, 0);
     }
 
diff --git a/include/net/net.h b/include/net/net.h
index ac59b593ba48..2c3b22f564b6 100644
--- a/include/net/net.h
+++ b/include/net/net.h
@@ -67,6 +67,7 @@ typedef void (SocketReadStateFinalize)(SocketReadState *rs);
 typedef void (NetAnnounce)(NetClientState *);
 typedef bool (SetSteeringEBPF)(NetClientState *, int);
 typedef bool (NetCheckPeerType)(NetClientState *, ObjectClass *, Error **);
+typedef bool (IsVHostUser)(NetClientState *);
 
 typedef struct NetClientInfo {
     NetClientDriver type;
@@ -92,6 +93,7 @@ typedef struct NetClientInfo {
     NetAnnounce *announce;
     SetSteeringEBPF *set_steering_ebpf;
     NetCheckPeerType *check_peer_type;
+    IsVHostUser *is_vhost_user;
 } NetClientInfo;
 
 struct NetClientState {
@@ -191,6 +193,7 @@ int qemu_get_vnet_hdr_len(NetClientState *nc);
 void qemu_set_vnet_hdr_len(NetClientState *nc, int len);
 int qemu_set_vnet_le(NetClientState *nc, bool is_le);
 int qemu_set_vnet_be(NetClientState *nc, bool is_be);
+bool qemu_is_vhost_user(NetClientState *nc);
 void qemu_macaddr_default_if_unset(MACAddr *macaddr);
 /**
  * qemu_find_nic_info: Obtain NIC configuration information
diff --git a/net/net.c b/net/net.c
index cfa2d8e95827..8726c59915a3 100644
--- a/net/net.c
+++ b/net/net.c
@@ -586,6 +586,15 @@ int qemu_set_vnet_le(NetClientState *nc, bool is_le)
 #endif
 }
 
+bool qemu_is_vhost_user(NetClientState *nc)
+{
+    if (nc->info->is_vhost_user) {
+        return nc->info->is_vhost_user(nc);
+    }
+
+    return false;
+}
+
 int qemu_set_vnet_be(NetClientState *nc, bool is_be)
 {
 #if HOST_BIG_ENDIAN
diff --git a/net/vhost-user.c b/net/vhost-user.c
index 10ac8dc0b3d7..4b4d02c56740 100644
--- a/net/vhost-user.c
+++ b/net/vhost-user.c
@@ -221,6 +221,13 @@ static bool vhost_user_check_peer_type(NetClientState *nc, ObjectClass *oc,
     return true;
 }
 
+static bool vhost_user_is_vhost_user(NetClientState *nc)
+{
+    assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);
+
+    return true;
+}
+
 static NetClientInfo net_vhost_user_info = {
         .type = NET_CLIENT_DRIVER_VHOST_USER,
         .size = sizeof(NetVhostUserState),
@@ -231,6 +238,7 @@ static NetClientInfo net_vhost_user_info = {
         .set_vnet_be = vhost_user_set_vnet_endianness,
         .set_vnet_le = vhost_user_set_vnet_endianness,
         .check_peer_type = vhost_user_check_peer_type,
+        .is_vhost_user = vhost_user_is_vhost_user,
 };
 
 static gboolean net_vhost_user_watch(void *do_not_use, GIOCondition cond,
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 4/8] net: Add get_vhost_net callback to NetClientInfo
  2025-06-18  8:39 [PATCH 0/8] net: Add passt netdev backend Laurent Vivier
                   ` (2 preceding siblings ...)
  2025-06-18  8:39 ` [PATCH 3/8] net: Introduce helper to identify vhost-user clients Laurent Vivier
@ 2025-06-18  8:39 ` Laurent Vivier
  2025-06-18  8:39 ` [PATCH 5/8] net: Add get_acked_features " Laurent Vivier
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 13+ messages in thread
From: Laurent Vivier @ 2025-06-18  8:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Michael S. Tsirkin, Eric Blake, Stefano Garzarella,
	Stefan Weil, Jason Wang, Philippe Mathieu-Daudé,
	Marc-André Lureau, Dr. David Alan Gilbert, Markus Armbruster,
	Daniel P. Berrangé, Laurent Vivier

The get_vhost_net() function previously contained a large switch
statement to find the VHostNetState pointer based on the net
client's type. This created a tight coupling, requiring the generic
vhost layer to be aware of every specific backend that supported
vhost, such as tap, vhost-user, and vhost-vdpa.

This approach is not scalable and requires modifying a central function
for any new backend. It also forced each backend to expose its internal
getter function in a public header file.

This patch refactors the logic by introducing a new get_vhost_net
function pointer to the NetClientInfo struct. The central
get_vhost_net() function is now a simple, generic dispatcher that
invokes the callback provided by the net client.

Each backend now implements its own private getter and registers it in
its NetClientInfo.

Signed-off-by: Laurent Vivier <lvivier@redhat.com>
---
 hw/net/vhost_net.c       | 31 ++++---------------------------
 include/net/net.h        |  2 ++
 include/net/tap.h        |  3 ---
 include/net/vhost-user.h |  1 -
 include/net/vhost-vdpa.h |  2 --
 net/tap-win32.c          |  5 -----
 net/tap.c                | 20 +++++++++++++-------
 net/vhost-user.c         |  3 ++-
 net/vhost-vdpa.c         |  4 +++-
 9 files changed, 24 insertions(+), 47 deletions(-)

diff --git a/hw/net/vhost_net.c b/hw/net/vhost_net.c
index 41d21e687ed9..16dadd022e75 100644
--- a/hw/net/vhost_net.c
+++ b/hw/net/vhost_net.c
@@ -646,41 +646,18 @@ void vhost_net_config_mask(VHostNetState *net, VirtIODevice *dev, bool mask)
 {
     vhost_config_mask(&net->dev, dev, mask);
 }
+
 VHostNetState *get_vhost_net(NetClientState *nc)
 {
-    VHostNetState *vhost_net = 0;
-
     if (!nc) {
         return 0;
     }
 
-    switch (nc->info->type) {
-    case NET_CLIENT_DRIVER_TAP:
-        vhost_net = tap_get_vhost_net(nc);
-        /*
-         * tap_get_vhost_net() can return NULL if a tap net-device backend is
-         * created with 'vhost=off' option, 'vhostforce=off' or no vhost or
-         * vhostforce or vhostfd options at all. Please see net_init_tap_one().
-         * Hence, we omit the assertion here.
-         */
-        break;
-#ifdef CONFIG_VHOST_NET_USER
-    case NET_CLIENT_DRIVER_VHOST_USER:
-        vhost_net = vhost_user_get_vhost_net(nc);
-        assert(vhost_net);
-        break;
-#endif
-#ifdef CONFIG_VHOST_NET_VDPA
-    case NET_CLIENT_DRIVER_VHOST_VDPA:
-        vhost_net = vhost_vdpa_get_vhost_net(nc);
-        assert(vhost_net);
-        break;
-#endif
-    default:
-        break;
+    if (nc->info->get_vhost_net) {
+        return nc->info->get_vhost_net(nc);
     }
 
-    return vhost_net;
+    return NULL;
 }
 
 int vhost_set_vring_enable(NetClientState *nc, int enable)
diff --git a/include/net/net.h b/include/net/net.h
index 2c3b22f564b6..8a62cd6e8aab 100644
--- a/include/net/net.h
+++ b/include/net/net.h
@@ -68,6 +68,7 @@ typedef void (NetAnnounce)(NetClientState *);
 typedef bool (SetSteeringEBPF)(NetClientState *, int);
 typedef bool (NetCheckPeerType)(NetClientState *, ObjectClass *, Error **);
 typedef bool (IsVHostUser)(NetClientState *);
+typedef struct vhost_net *(GetVHostNet)(NetClientState *nc);
 
 typedef struct NetClientInfo {
     NetClientDriver type;
@@ -94,6 +95,7 @@ typedef struct NetClientInfo {
     SetSteeringEBPF *set_steering_ebpf;
     NetCheckPeerType *check_peer_type;
     IsVHostUser *is_vhost_user;
+    GetVHostNet *get_vhost_net;
 } NetClientInfo;
 
 struct NetClientState {
diff --git a/include/net/tap.h b/include/net/tap.h
index 5d585515f9e3..6f34f13eae44 100644
--- a/include/net/tap.h
+++ b/include/net/tap.h
@@ -33,7 +33,4 @@ int tap_disable(NetClientState *nc);
 
 int tap_get_fd(NetClientState *nc);
 
-struct vhost_net;
-struct vhost_net *tap_get_vhost_net(NetClientState *nc);
-
 #endif /* QEMU_NET_TAP_H */
diff --git a/include/net/vhost-user.h b/include/net/vhost-user.h
index 35bf61970985..0b233a267345 100644
--- a/include/net/vhost-user.h
+++ b/include/net/vhost-user.h
@@ -12,7 +12,6 @@
 #define VHOST_USER_H
 
 struct vhost_net;
-struct vhost_net *vhost_user_get_vhost_net(NetClientState *nc);
 uint64_t vhost_user_get_acked_features(NetClientState *nc);
 void vhost_user_save_acked_features(NetClientState *nc);
 
diff --git a/include/net/vhost-vdpa.h b/include/net/vhost-vdpa.h
index b81f9a6f2a0e..916ead3793d9 100644
--- a/include/net/vhost-vdpa.h
+++ b/include/net/vhost-vdpa.h
@@ -14,8 +14,6 @@
 
 #define TYPE_VHOST_VDPA "vhost-vdpa"
 
-struct vhost_net *vhost_vdpa_get_vhost_net(NetClientState *nc);
-
 extern const int vdpa_feature_bits[];
 
 #endif /* VHOST_VDPA_H */
diff --git a/net/tap-win32.c b/net/tap-win32.c
index 671dee970f7a..38baf90e0b3f 100644
--- a/net/tap-win32.c
+++ b/net/tap-win32.c
@@ -704,11 +704,6 @@ static void tap_win32_send(void *opaque)
     }
 }
 
-struct vhost_net *tap_get_vhost_net(NetClientState *nc)
-{
-    return NULL;
-}
-
 static NetClientInfo net_tap_win32_info = {
     .type = NET_CLIENT_DRIVER_TAP,
     .size = sizeof(TAPState),
diff --git a/net/tap.c b/net/tap.c
index ae1c7e398321..4beba6d7a784 100644
--- a/net/tap.c
+++ b/net/tap.c
@@ -329,6 +329,18 @@ int tap_get_fd(NetClientState *nc)
     return s->fd;
 }
 
+/*
+ * tap_get_vhost_net() can return NULL if a tap net-device backend is
+ * created with 'vhost=off' option, 'vhostforce=off' or no vhost or
+ * vhostforce or vhostfd options at all. Please see net_init_tap_one().
+ */
+static VHostNetState *tap_get_vhost_net(NetClientState *nc)
+{
+    TAPState *s = DO_UPCAST(TAPState, nc, nc);
+    assert(nc->info->type == NET_CLIENT_DRIVER_TAP);
+    return s->vhost_net;
+}
+
 /* fd support */
 
 static NetClientInfo net_tap_info = {
@@ -347,6 +359,7 @@ static NetClientInfo net_tap_info = {
     .set_vnet_le = tap_set_vnet_le,
     .set_vnet_be = tap_set_vnet_be,
     .set_steering_ebpf = tap_set_steering_ebpf,
+    .get_vhost_net = tap_get_vhost_net,
 };
 
 static TAPState *net_tap_fd_init(NetClientState *peer,
@@ -980,13 +993,6 @@ free_fail:
     return 0;
 }
 
-VHostNetState *tap_get_vhost_net(NetClientState *nc)
-{
-    TAPState *s = DO_UPCAST(TAPState, nc, nc);
-    assert(nc->info->type == NET_CLIENT_DRIVER_TAP);
-    return s->vhost_net;
-}
-
 int tap_enable(NetClientState *nc)
 {
     TAPState *s = DO_UPCAST(TAPState, nc, nc);
diff --git a/net/vhost-user.c b/net/vhost-user.c
index 4b4d02c56740..2caa7e56f7a0 100644
--- a/net/vhost-user.c
+++ b/net/vhost-user.c
@@ -32,7 +32,7 @@ typedef struct NetVhostUserState {
     bool started;
 } NetVhostUserState;
 
-VHostNetState *vhost_user_get_vhost_net(NetClientState *nc)
+static struct vhost_net *vhost_user_get_vhost_net(NetClientState *nc)
 {
     NetVhostUserState *s = DO_UPCAST(NetVhostUserState, nc, nc);
     assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);
@@ -239,6 +239,7 @@ static NetClientInfo net_vhost_user_info = {
         .set_vnet_le = vhost_user_set_vnet_endianness,
         .check_peer_type = vhost_user_check_peer_type,
         .is_vhost_user = vhost_user_is_vhost_user,
+        .get_vhost_net = vhost_user_get_vhost_net,
 };
 
 static gboolean net_vhost_user_watch(void *do_not_use, GIOCondition cond,
diff --git a/net/vhost-vdpa.c b/net/vhost-vdpa.c
index 58d738945dbc..0b86c917ed68 100644
--- a/net/vhost-vdpa.c
+++ b/net/vhost-vdpa.c
@@ -132,7 +132,7 @@ static const uint64_t vdpa_svq_device_features =
 
 #define VHOST_VDPA_NET_CVQ_ASID 1
 
-VHostNetState *vhost_vdpa_get_vhost_net(NetClientState *nc)
+static struct vhost_net *vhost_vdpa_get_vhost_net(NetClientState *nc)
 {
     VhostVDPAState *s = DO_UPCAST(VhostVDPAState, nc, nc);
     assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_VDPA);
@@ -432,6 +432,7 @@ static NetClientInfo net_vhost_vdpa_info = {
         .set_vnet_le = vhost_vdpa_set_vnet_le,
         .check_peer_type = vhost_vdpa_check_peer_type,
         .set_steering_ebpf = vhost_vdpa_set_steering_ebpf,
+        .get_vhost_net = vhost_vdpa_get_vhost_net,
 };
 
 static int64_t vhost_vdpa_get_vring_group(int device_fd, unsigned vq_index,
@@ -1287,6 +1288,7 @@ static NetClientInfo net_vhost_vdpa_cvq_info = {
     .has_ufo = vhost_vdpa_has_ufo,
     .check_peer_type = vhost_vdpa_check_peer_type,
     .set_steering_ebpf = vhost_vdpa_set_steering_ebpf,
+    .get_vhost_net = vhost_vdpa_get_vhost_net,
 };
 
 /*
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 5/8] net: Add get_acked_features callback to NetClientInfo
  2025-06-18  8:39 [PATCH 0/8] net: Add passt netdev backend Laurent Vivier
                   ` (3 preceding siblings ...)
  2025-06-18  8:39 ` [PATCH 4/8] net: Add get_vhost_net callback to NetClientInfo Laurent Vivier
@ 2025-06-18  8:39 ` Laurent Vivier
  2025-06-18  8:39 ` [PATCH 6/8] net: Add save_acked_features " Laurent Vivier
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 13+ messages in thread
From: Laurent Vivier @ 2025-06-18  8:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Michael S. Tsirkin, Eric Blake, Stefano Garzarella,
	Stefan Weil, Jason Wang, Philippe Mathieu-Daudé,
	Marc-André Lureau, Dr. David Alan Gilbert, Markus Armbruster,
	Daniel P. Berrangé, Laurent Vivier

This patch continues the effort to decouple the generic vhost layer
from specific network backend implementations.

Previously, the vhost_net initialization code contained a hardcoded
check for the vhost-user client type to retrieve its acked features
by calling vhost_user_get_acked_features(). This exposed an
internal vhost-user function in a public header and coupled the two
modules.

Following the pattern of recent commits, this patch introduces a
generic helper, qemu_get_acked_features(), and a corresponding
get_acked_features callback in the NetClientInfo struct.

The vhost-user backend is updated to provide this callback, and its
getter function is now static. The call site in vhost_net.c is
simplified to use the new generic helper, removing the type check and
the direct dependency.

Signed-off-by: Laurent Vivier <lvivier@redhat.com>
---
 hw/net/vhost_net.c       | 18 +++++++-----------
 include/net/net.h        |  4 ++++
 include/net/vhost-user.h |  2 --
 net/net.c                | 10 ++++++++++
 net/vhost-user.c         |  3 ++-
 5 files changed, 23 insertions(+), 14 deletions(-)

diff --git a/hw/net/vhost_net.c b/hw/net/vhost_net.c
index 16dadd022e75..4ed28a6d4186 100644
--- a/hw/net/vhost_net.c
+++ b/hw/net/vhost_net.c
@@ -317,8 +317,8 @@ struct vhost_net *vhost_net_init(VhostNetOptions *options)
     int r;
     bool backend_kernel = options->backend_type == VHOST_BACKEND_TYPE_KERNEL;
     struct vhost_net *net = g_new0(struct vhost_net, 1);
-    uint64_t features = 0;
     Error *local_err = NULL;
+    uint64_t features;
 
     if (!options->net_backend) {
         fprintf(stderr, "vhost-net requires net backend to be setup\n");
@@ -369,17 +369,13 @@ struct vhost_net *vhost_net_init(VhostNetOptions *options)
     }
 
     /* Set sane init value. Override when guest acks. */
-#ifdef CONFIG_VHOST_NET_USER
-    if (net->nc->info->type == NET_CLIENT_DRIVER_VHOST_USER) {
-        features = vhost_user_get_acked_features(net->nc);
-        if (~net->dev.features & features) {
-            fprintf(stderr, "vhost lacks feature mask 0x%" PRIx64
-                    " for backend\n",
-                    (uint64_t)(~net->dev.features & features));
-            goto fail;
-        }
+    features = qemu_get_acked_features(net->nc);
+    if (~net->dev.features & features) {
+        fprintf(stderr, "vhost lacks feature mask 0x%" PRIx64
+                " for backend\n",
+                (uint64_t)(~net->dev.features & features));
+        goto fail;
     }
-#endif
 
     vhost_net_ack_features(net, features);
 
diff --git a/include/net/net.h b/include/net/net.h
index 8a62cd6e8aab..ed9febd378b6 100644
--- a/include/net/net.h
+++ b/include/net/net.h
@@ -69,6 +69,7 @@ typedef bool (SetSteeringEBPF)(NetClientState *, int);
 typedef bool (NetCheckPeerType)(NetClientState *, ObjectClass *, Error **);
 typedef bool (IsVHostUser)(NetClientState *);
 typedef struct vhost_net *(GetVHostNet)(NetClientState *nc);
+typedef uint64_t (GetAckedFeatures)(NetClientState *nc);
 
 typedef struct NetClientInfo {
     NetClientDriver type;
@@ -96,6 +97,7 @@ typedef struct NetClientInfo {
     NetCheckPeerType *check_peer_type;
     IsVHostUser *is_vhost_user;
     GetVHostNet *get_vhost_net;
+    GetAckedFeatures *get_acked_features;
 } NetClientInfo;
 
 struct NetClientState {
@@ -197,6 +199,8 @@ int qemu_set_vnet_le(NetClientState *nc, bool is_le);
 int qemu_set_vnet_be(NetClientState *nc, bool is_be);
 bool qemu_is_vhost_user(NetClientState *nc);
 void qemu_macaddr_default_if_unset(MACAddr *macaddr);
+uint64_t qemu_get_acked_features(NetClientState *nc);
+
 /**
  * qemu_find_nic_info: Obtain NIC configuration information
  * @typename: Name of device object type
diff --git a/include/net/vhost-user.h b/include/net/vhost-user.h
index 0b233a267345..a4d0ce4b8dd1 100644
--- a/include/net/vhost-user.h
+++ b/include/net/vhost-user.h
@@ -11,8 +11,6 @@
 #ifndef VHOST_USER_H
 #define VHOST_USER_H
 
-struct vhost_net;
-uint64_t vhost_user_get_acked_features(NetClientState *nc);
 void vhost_user_save_acked_features(NetClientState *nc);
 
 #endif /* VHOST_USER_H */
diff --git a/net/net.c b/net/net.c
index 8726c59915a3..ba051441053f 100644
--- a/net/net.c
+++ b/net/net.c
@@ -608,6 +608,16 @@ int qemu_set_vnet_be(NetClientState *nc, bool is_be)
 #endif
 }
 
+uint64_t qemu_get_acked_features(NetClientState *nc)
+{
+    if (nc->info->get_acked_features) {
+        return nc->info->get_acked_features(nc);
+    }
+
+    return 0;
+}
+
+
 int qemu_can_receive_packet(NetClientState *nc)
 {
     if (nc->receive_disabled) {
diff --git a/net/vhost-user.c b/net/vhost-user.c
index 2caa7e56f7a0..45c952b1e76d 100644
--- a/net/vhost-user.c
+++ b/net/vhost-user.c
@@ -39,7 +39,7 @@ static struct vhost_net *vhost_user_get_vhost_net(NetClientState *nc)
     return s->vhost_net;
 }
 
-uint64_t vhost_user_get_acked_features(NetClientState *nc)
+static uint64_t vhost_user_get_acked_features(NetClientState *nc)
 {
     NetVhostUserState *s = DO_UPCAST(NetVhostUserState, nc, nc);
     assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_USER);
@@ -240,6 +240,7 @@ static NetClientInfo net_vhost_user_info = {
         .check_peer_type = vhost_user_check_peer_type,
         .is_vhost_user = vhost_user_is_vhost_user,
         .get_vhost_net = vhost_user_get_vhost_net,
+        .get_acked_features = vhost_user_get_acked_features,
 };
 
 static gboolean net_vhost_user_watch(void *do_not_use, GIOCondition cond,
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 6/8] net: Add save_acked_features callback to NetClientInfo
  2025-06-18  8:39 [PATCH 0/8] net: Add passt netdev backend Laurent Vivier
                   ` (4 preceding siblings ...)
  2025-06-18  8:39 ` [PATCH 5/8] net: Add get_acked_features " Laurent Vivier
@ 2025-06-18  8:39 ` Laurent Vivier
  2025-06-18  8:39 ` [PATCH 7/8] net: Add passt network backend Laurent Vivier
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 13+ messages in thread
From: Laurent Vivier @ 2025-06-18  8:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Michael S. Tsirkin, Eric Blake, Stefano Garzarella,
	Stefan Weil, Jason Wang, Philippe Mathieu-Daudé,
	Marc-André Lureau, Dr. David Alan Gilbert, Markus Armbruster,
	Daniel P. Berrangé, Laurent Vivier

This patch completes the series of refactorings aimed at decoupling the
generic vhost layer from specific network backends.

The final remaining dependency was in vhost_net_save_acked_features,
which contained a hardcoded check for the vhost-user client type.

This commit applies the now-established callback pattern, introducing a
save_acked_features function pointer to NetClientInfo and
converting the vhost_net function into a generic dispatcher.

The vhost-user backend provides the callback, making its function static.
With this change, no other module has a direct dependency on the
vhost-user implementation.

This cleanup allows for the complete removal of the net/vhost-user.h
header file.

Signed-off-by: Laurent Vivier <lvivier@redhat.com>
---
 hw/net/vhost_net-stub.c  |  1 -
 hw/net/vhost_net.c       |  7 ++-----
 include/net/net.h        |  2 ++
 include/net/vhost-user.h | 16 ----------------
 net/vhost-user-stub.c    |  1 -
 net/vhost-user.c         |  4 ++--
 6 files changed, 6 insertions(+), 25 deletions(-)
 delete mode 100644 include/net/vhost-user.h

diff --git a/hw/net/vhost_net-stub.c b/hw/net/vhost_net-stub.c
index 72df6d757e4d..dab9943172da 100644
--- a/hw/net/vhost_net-stub.c
+++ b/hw/net/vhost_net-stub.c
@@ -13,7 +13,6 @@
 #include "qemu/osdep.h"
 #include "net/net.h"
 #include "net/tap.h"
-#include "net/vhost-user.h"
 
 #include "hw/virtio/virtio-net.h"
 #include "net/vhost_net.h"
diff --git a/hw/net/vhost_net.c b/hw/net/vhost_net.c
index 4ed28a6d4186..756af26db207 100644
--- a/hw/net/vhost_net.c
+++ b/hw/net/vhost_net.c
@@ -16,7 +16,6 @@
 #include "qemu/osdep.h"
 #include "net/net.h"
 #include "net/tap.h"
-#include "net/vhost-user.h"
 #include "net/vhost-vdpa.h"
 
 #include "standard-headers/linux/vhost_types.h"
@@ -152,11 +151,9 @@ uint64_t vhost_net_get_acked_features(VHostNetState *net)
 
 void vhost_net_save_acked_features(NetClientState *nc)
 {
-#ifdef CONFIG_VHOST_NET_USER
-    if (nc->info->type == NET_CLIENT_DRIVER_VHOST_USER) {
-        vhost_user_save_acked_features(nc);
+    if (nc->info->save_acked_features) {
+        nc->info->save_acked_features(nc);
     }
-#endif
 }
 
 static void vhost_net_disable_notifiers_nvhosts(VirtIODevice *dev,
diff --git a/include/net/net.h b/include/net/net.h
index ed9febd378b6..5ef86cd6c384 100644
--- a/include/net/net.h
+++ b/include/net/net.h
@@ -70,6 +70,7 @@ typedef bool (NetCheckPeerType)(NetClientState *, ObjectClass *, Error **);
 typedef bool (IsVHostUser)(NetClientState *);
 typedef struct vhost_net *(GetVHostNet)(NetClientState *nc);
 typedef uint64_t (GetAckedFeatures)(NetClientState *nc);
+typedef void (SaveAcketFeatures)(NetClientState *nc);
 
 typedef struct NetClientInfo {
     NetClientDriver type;
@@ -98,6 +99,7 @@ typedef struct NetClientInfo {
     IsVHostUser *is_vhost_user;
     GetVHostNet *get_vhost_net;
     GetAckedFeatures *get_acked_features;
+    SaveAcketFeatures *save_acked_features;
 } NetClientInfo;
 
 struct NetClientState {
diff --git a/include/net/vhost-user.h b/include/net/vhost-user.h
deleted file mode 100644
index a4d0ce4b8dd1..000000000000
--- a/include/net/vhost-user.h
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
- * vhost-user.h
- *
- * Copyright (c) 2013 Virtual Open Systems Sarl.
- *
- * This work is licensed under the terms of the GNU GPL, version 2 or later.
- * See the COPYING file in the top-level directory.
- *
- */
-
-#ifndef VHOST_USER_H
-#define VHOST_USER_H
-
-void vhost_user_save_acked_features(NetClientState *nc);
-
-#endif /* VHOST_USER_H */
diff --git a/net/vhost-user-stub.c b/net/vhost-user-stub.c
index 52ab4e13f12a..283dee87db2d 100644
--- a/net/vhost-user-stub.c
+++ b/net/vhost-user-stub.c
@@ -11,7 +11,6 @@
 #include "qemu/osdep.h"
 #include "clients.h"
 #include "net/vhost_net.h"
-#include "net/vhost-user.h"
 #include "qemu/error-report.h"
 #include "qapi/error.h"
 
diff --git a/net/vhost-user.c b/net/vhost-user.c
index 45c952b1e76d..5b9f08163fc5 100644
--- a/net/vhost-user.c
+++ b/net/vhost-user.c
@@ -11,7 +11,6 @@
 #include "qemu/osdep.h"
 #include "clients.h"
 #include "net/vhost_net.h"
-#include "net/vhost-user.h"
 #include "hw/virtio/vhost-user.h"
 #include "chardev/char-fe.h"
 #include "qapi/error.h"
@@ -46,7 +45,7 @@ static uint64_t vhost_user_get_acked_features(NetClientState *nc)
     return s->acked_features;
 }
 
-void vhost_user_save_acked_features(NetClientState *nc)
+static void vhost_user_save_acked_features(NetClientState *nc)
 {
     NetVhostUserState *s;
 
@@ -241,6 +240,7 @@ static NetClientInfo net_vhost_user_info = {
         .is_vhost_user = vhost_user_is_vhost_user,
         .get_vhost_net = vhost_user_get_vhost_net,
         .get_acked_features = vhost_user_get_acked_features,
+        .save_acked_features = vhost_user_save_acked_features,
 };
 
 static gboolean net_vhost_user_watch(void *do_not_use, GIOCondition cond,
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 7/8] net: Add passt network backend
  2025-06-18  8:39 [PATCH 0/8] net: Add passt netdev backend Laurent Vivier
                   ` (5 preceding siblings ...)
  2025-06-18  8:39 ` [PATCH 6/8] net: Add save_acked_features " Laurent Vivier
@ 2025-06-18  8:39 ` Laurent Vivier
  2025-06-18  8:39 ` [PATCH 8/8] net/passt: Implement vhost-user backend support Laurent Vivier
  2025-06-23  8:03 ` [PATCH 0/8] net: Add passt netdev backend Jason Wang
  8 siblings, 0 replies; 13+ messages in thread
From: Laurent Vivier @ 2025-06-18  8:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Michael S. Tsirkin, Eric Blake, Stefano Garzarella,
	Stefan Weil, Jason Wang, Philippe Mathieu-Daudé,
	Marc-André Lureau, Dr. David Alan Gilbert, Markus Armbruster,
	Daniel P. Berrangé, Laurent Vivier

This commit introduces support for passt as a new network backend.
passt is an unprivileged, user-mode networking solution that provides
connectivity for virtual machines by launching an external helper process.

The implementation reuses the generic stream data handling logic. It
launches the passt binary using GSubprocess, passing it a file
descriptor from a socketpair() for communication. QEMU connects to
the other end of the socket pair to establish the network data stream.

The PID of the passt daemon is tracked via a temporary file to
ensure it is terminated when QEMU exits.

Signed-off-by: Laurent Vivier <lvivier@redhat.com>
---
 hmp-commands.hx   |   3 +
 meson.build       |   6 +
 meson_options.txt |   2 +
 net/clients.h     |   4 +
 net/hub.c         |   3 +
 net/meson.build   |   3 +
 net/net.c         |   4 +
 net/passt.c       | 430 ++++++++++++++++++++++++++++++++++++++++++++++
 qapi/net.json     | 121 +++++++++++++
 qemu-options.hx   |  18 ++
 10 files changed, 594 insertions(+)
 create mode 100644 net/passt.c

diff --git a/hmp-commands.hx b/hmp-commands.hx
index 06746f0afc37..d0e4f35a30af 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -1287,6 +1287,9 @@ ERST
         .name       = "netdev_add",
         .args_type  = "netdev:O",
         .params     = "[user|tap|socket|stream|dgram|vde|bridge|hubport|netmap|vhost-user"
+#ifdef CONFIG_PASST
+                      "|passt"
+#endif
 #ifdef CONFIG_AF_XDP
                       "|af-xdp"
 #endif
diff --git a/meson.build b/meson.build
index 34729c2a3dd5..485a60a0cb0c 100644
--- a/meson.build
+++ b/meson.build
@@ -1288,6 +1288,10 @@ if not get_option('slirp').auto() or have_system
   endif
 endif
 
+enable_passt = get_option('passt') \
+  .require(host_os == 'linux', error_message: 'passt is supported only on Linux') \
+  .allowed()
+
 vde = not_found
 if not get_option('vde').auto() or have_system or have_tools
   vde = cc.find_library('vdeplug', has_headers: ['libvdeplug.h'],
@@ -2541,6 +2545,7 @@ if seccomp.found()
   config_host_data.set('CONFIG_SECCOMP_SYSRAWRC', seccomp_has_sysrawrc)
 endif
 config_host_data.set('CONFIG_PIXMAN', pixman.found())
+config_host_data.set('CONFIG_PASST', enable_passt)
 config_host_data.set('CONFIG_SLIRP', slirp.found())
 config_host_data.set('CONFIG_SNAPPY', snappy.found())
 config_host_data.set('CONFIG_SOLARIS', host_os == 'sunos')
@@ -4965,6 +4970,7 @@ if host_os == 'darwin'
   summary_info += {'vmnet.framework support': vmnet}
 endif
 summary_info += {'AF_XDP support':    libxdp}
+summary_info += {'passt support':     enable_passt}
 summary_info += {'slirp support':     slirp}
 summary_info += {'vde support':       vde}
 summary_info += {'netmap support':    have_netmap}
diff --git a/meson_options.txt b/meson_options.txt
index a442be29958f..3146eec19440 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -234,6 +234,8 @@ option('pixman', type : 'feature', value : 'auto',
        description: 'pixman support')
 option('slirp', type: 'feature', value: 'auto',
        description: 'libslirp user mode network backend support')
+option('passt', type: 'feature', value: 'auto',
+       description: 'passt network backend support')
 option('vde', type : 'feature', value : 'auto',
        description: 'vde network backend support')
 option('vmnet', type : 'feature', value : 'auto',
diff --git a/net/clients.h b/net/clients.h
index be53794582cf..e786ab420352 100644
--- a/net/clients.h
+++ b/net/clients.h
@@ -29,6 +29,10 @@
 int net_init_dump(const Netdev *netdev, const char *name,
                   NetClientState *peer, Error **errp);
 
+#ifdef CONFIG_PASST
+int net_init_passt(const Netdev *netdev, const char *name,
+                   NetClientState *peer, Error **errp);
+#endif
 #ifdef CONFIG_SLIRP
 int net_init_slirp(const Netdev *netdev, const char *name,
                    NetClientState *peer, Error **errp);
diff --git a/net/hub.c b/net/hub.c
index cba20ebd874f..e3b58b1c4f8e 100644
--- a/net/hub.c
+++ b/net/hub.c
@@ -285,6 +285,9 @@ void net_hub_check_clients(void)
             case NET_CLIENT_DRIVER_NIC:
                 has_nic = 1;
                 break;
+#ifdef CONFIG_PASST
+            case NET_CLIENT_DRIVER_PASST:
+#endif
             case NET_CLIENT_DRIVER_USER:
             case NET_CLIENT_DRIVER_TAP:
             case NET_CLIENT_DRIVER_SOCKET:
diff --git a/net/meson.build b/net/meson.build
index bb3c011e5a30..da6ea635e95d 100644
--- a/net/meson.build
+++ b/net/meson.build
@@ -34,6 +34,9 @@ system_ss.add(when: 'CONFIG_TCG', if_true: files('filter-replay.c'))
 if have_l2tpv3
   system_ss.add(files('l2tpv3.c'))
 endif
+if enable_passt
+  system_ss.add(files('passt.c'))
+endif
 system_ss.add(when: slirp, if_true: files('slirp.c'))
 system_ss.add(when: vde, if_true: files('vde.c'))
 if have_netmap
diff --git a/net/net.c b/net/net.c
index ba051441053f..e6789378809c 100644
--- a/net/net.c
+++ b/net/net.c
@@ -1267,6 +1267,9 @@ static int (* const net_client_init_fun[NET_CLIENT_DRIVER__MAX])(
     const char *name,
     NetClientState *peer, Error **errp) = {
         [NET_CLIENT_DRIVER_NIC]       = net_init_nic,
+#ifdef CONFIG_PASST
+        [NET_CLIENT_DRIVER_PASST]     = net_init_passt,
+#endif
 #ifdef CONFIG_SLIRP
         [NET_CLIENT_DRIVER_USER]      = net_init_slirp,
 #endif
@@ -1372,6 +1375,7 @@ void show_netdevs(void)
         "dgram",
         "hubport",
         "tap",
+        "passt",
 #ifdef CONFIG_SLIRP
         "user",
 #endif
diff --git a/net/passt.c b/net/passt.c
new file mode 100644
index 000000000000..c3b89e779c65
--- /dev/null
+++ b/net/passt.c
@@ -0,0 +1,430 @@
+/*
+ * passt network backend
+ *
+ * Copyright Red Hat
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+#include <gio/gio.h>
+#include "net/net.h"
+#include "clients.h"
+#include "qapi/error.h"
+#include "io/net-listener.h"
+#include "stream_data.h"
+
+typedef struct NetPasstState {
+    NetStreamData data;
+    GPtrArray *args;
+    gchar *pidfile;
+    pid_t pid;
+} NetPasstState;
+
+static int net_passt_stream_start(NetPasstState *s, Error **errp);
+
+static void net_passt_cleanup(NetClientState *nc)
+{
+    NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
+
+    kill(s->pid, SIGTERM);
+    g_remove(s->pidfile);
+    g_free(s->pidfile);
+    g_ptr_array_free(s->args, TRUE);
+}
+
+static ssize_t net_passt_receive(NetClientState *nc, const uint8_t *buf,
+                                  size_t size)
+{
+    NetStreamData *d = DO_UPCAST(NetStreamData, nc, nc);
+
+    return net_stream_data_receive(d, buf, size);
+}
+
+static gboolean net_passt_send(QIOChannel *ioc, GIOCondition condition,
+                                gpointer data)
+{
+    if (net_stream_data_send(ioc, condition, data) == G_SOURCE_REMOVE) {
+        NetPasstState *s = DO_UPCAST(NetPasstState, data, data);
+        Error *error;
+
+        /* we need to restart passt */
+        kill(s->pid, SIGTERM);
+        if (net_passt_stream_start(s, &error) == -1) {
+            error_report_err(error);
+        }
+
+        return G_SOURCE_REMOVE;
+    }
+
+    return G_SOURCE_CONTINUE;
+}
+
+static NetClientInfo net_passt_info = {
+    .type = NET_CLIENT_DRIVER_PASST,
+    .size = sizeof(NetPasstState),
+    .receive = net_passt_receive,
+    .cleanup = net_passt_cleanup,
+};
+
+static void net_passt_client_connected(QIOTask *task, gpointer opaque)
+{
+    NetPasstState *s = opaque;
+
+    if (net_stream_data_client_connected(task, &s->data) == 0) {
+        qemu_set_info_str(&s->data.nc, "stream,connected to pid %d", s->pid);
+    }
+}
+
+static int net_passt_start_daemon(NetPasstState *s, int sock, Error **errp)
+{
+    g_autoptr(GSubprocess) daemon = NULL;
+    g_autofree gchar *contents = NULL;
+    g_autoptr(GError) error = NULL;
+    GSubprocessLauncher *launcher;
+
+    qemu_set_info_str(&s->data.nc, "launching passt");
+
+    launcher = g_subprocess_launcher_new(G_SUBPROCESS_FLAGS_NONE);
+    g_subprocess_launcher_take_fd(launcher, sock, 3);
+
+    daemon =  g_subprocess_launcher_spawnv(launcher,
+                                           (const gchar *const *)s->args->pdata,
+                                           &error);
+    g_object_unref(launcher);
+
+    if (!daemon) {
+        error_setg(errp, "Error creating daemon: %s", error->message);
+        return -1;
+    }
+
+    if (!g_subprocess_wait(daemon, NULL, &error)) {
+        error_setg(errp, "Error waiting for daemon: %s", error->message);
+        return -1;
+    }
+
+    if (g_subprocess_get_if_exited(daemon) &&
+        g_subprocess_get_exit_status(daemon)) {
+        return -1;
+    }
+
+    if (!g_file_get_contents(s->pidfile, &contents, NULL, &error)) {
+        error_setg(errp, "Cannot read passt pid: %s", error->message);
+        return -1;
+    }
+
+    s->pid = (pid_t)g_ascii_strtoll(contents, NULL, 10);
+    if (s->pid <= 0) {
+        error_setg(errp, "File '%s' did not contain a valid PID.", s->pidfile);
+        return -1;
+    }
+
+    return 0;
+}
+
+static int net_passt_stream_start(NetPasstState *s, Error **errp)
+{
+    QIOChannelSocket *sioc;
+    SocketAddress *addr;
+    int sv[2];
+
+    if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) {
+        error_setg_errno(errp, errno, "socketpair() failed");
+        return -1;
+    }
+
+    /* connect to passt */
+    qemu_set_info_str(&s->data.nc, "connecting to passt");
+
+    /* create socket channel */
+    sioc = qio_channel_socket_new();
+    s->data.ioc = QIO_CHANNEL(sioc);
+    s->data.nc.link_down = true;
+    s->data.send = net_passt_send;
+
+    addr = g_new0(SocketAddress, 1);
+    addr->type = SOCKET_ADDRESS_TYPE_FD;
+    addr->u.fd.str = g_strdup_printf("%d", sv[0]);
+
+    qio_channel_socket_connect_async(sioc, addr,
+                                     net_passt_client_connected, s,
+                                     NULL, NULL);
+
+    qapi_free_SocketAddress(addr);
+
+    /* start passt */
+    if (net_passt_start_daemon(s, sv[1], errp) == -1) {
+        close(sv[0]);
+        close(sv[1]);
+        return -1;
+    }
+    close(sv[1]);
+
+    return 0;
+}
+
+static int net_passt_vhost_user_init(NetPasstState *s, Error **errp)
+
+{
+    error_setg(errp, "vhost-user parameter not yet implemented");
+
+    return -1;
+}
+
+static GPtrArray *net_passt_decode_args(const NetDevPasstOptions *passt,
+                                        gchar *pidfile, Error **errp)
+{
+    GPtrArray *args = g_ptr_array_new_with_free_func(g_free);
+
+    g_ptr_array_add(args, g_strdup("passt"));
+
+    /* provide a pid file to be able to kil passt on exit */
+    g_ptr_array_add(args, g_strdup("--pid"));
+    g_ptr_array_add(args, g_strdup(pidfile));
+
+    /* g_subprocess_launcher_take_fd() will set the socket on fd 3 */
+    g_ptr_array_add(args, g_strdup("--fd"));
+    g_ptr_array_add(args, g_strdup("3"));
+
+    /* by default, be quiet */
+    if (!passt->has_quiet || passt->quiet) {
+        g_ptr_array_add(args, g_strdup("--quiet"));
+    }
+
+    if (passt->has_debug && passt->debug) {
+        g_ptr_array_add(args, g_strdup("--debug"));
+    }
+
+    if (passt->has_trace && passt->trace) {
+        g_ptr_array_add(args, g_strdup("--trace"));
+    }
+
+    if (passt->has_vhost_user && passt->vhost_user) {
+        g_ptr_array_add(args, g_strdup("--vhost-user"));
+    }
+
+    if (passt->pcap_file) {
+        g_ptr_array_add(args, g_strdup("--pcap"));
+        g_ptr_array_add(args, g_strdup(passt->pcap_file));
+    }
+
+    if (passt->has_mtu) {
+        g_ptr_array_add(args, g_strdup("--mtu"));
+        g_ptr_array_add(args, g_strdup_printf("%"PRId64, passt->mtu));
+    }
+
+    if (passt->address) {
+        g_ptr_array_add(args, g_strdup("--address"));
+        g_ptr_array_add(args, g_strdup(passt->address));
+    }
+
+    if (passt->netmask) {
+        g_ptr_array_add(args, g_strdup("--netmask"));
+        g_ptr_array_add(args, g_strdup(passt->netmask));
+    }
+
+    if (passt->mac) {
+        g_ptr_array_add(args, g_strdup("--mac-addr"));
+        g_ptr_array_add(args, g_strdup(passt->mac));
+    }
+
+    if (passt->gateway) {
+        g_ptr_array_add(args, g_strdup("--gateway"));
+        g_ptr_array_add(args, g_strdup(passt->gateway));
+    }
+
+    if (passt->interface) {
+        g_ptr_array_add(args, g_strdup("--interface"));
+        g_ptr_array_add(args, g_strdup(passt->interface));
+    }
+
+    if (passt->outbound) {
+        g_ptr_array_add(args, g_strdup("--outbound"));
+        g_ptr_array_add(args, g_strdup(passt->outbound));
+    }
+
+    if (passt->outbound_if4) {
+        g_ptr_array_add(args, g_strdup("--outbound-if4"));
+        g_ptr_array_add(args, g_strdup(passt->outbound_if4));
+    }
+
+    if (passt->outbound_if6) {
+        g_ptr_array_add(args, g_strdup("--outbound-if6"));
+        g_ptr_array_add(args, g_strdup(passt->outbound_if6));
+    }
+
+    if (passt->dns) {
+        g_ptr_array_add(args, g_strdup("--dns"));
+        g_ptr_array_add(args, g_strdup(passt->dns));
+    }
+
+    if (passt->fqdn) {
+        g_ptr_array_add(args, g_strdup("--fqdn"));
+        g_ptr_array_add(args, g_strdup(passt->fqdn));
+    }
+
+    if (passt->has_dhcp_dns && !passt->dhcp_dns) {
+        g_ptr_array_add(args, g_strdup("--no-dhcp-dns"));
+    }
+
+    if (passt->has_dhcp_search && !passt->dhcp_search) {
+        g_ptr_array_add(args, g_strdup("--no-dhcp-search"));
+    }
+
+    if (passt->map_host_loopback) {
+        g_ptr_array_add(args, g_strdup("--map-host-loopback"));
+        g_ptr_array_add(args, g_strdup(passt->map_host_loopback));
+    }
+
+    if (passt->map_guest_addr) {
+        g_ptr_array_add(args, g_strdup("--map-guest-addr"));
+        g_ptr_array_add(args, g_strdup(passt->map_guest_addr));
+    }
+
+    if (passt->dns_forward) {
+        g_ptr_array_add(args, g_strdup("--dns-forward"));
+        g_ptr_array_add(args, g_strdup(passt->dns_forward));
+    }
+
+    if (passt->dns_host) {
+        g_ptr_array_add(args, g_strdup("--dns-host"));
+        g_ptr_array_add(args, g_strdup(passt->dns_host));
+    }
+
+    if (passt->has_tcp && !passt->tcp) {
+        g_ptr_array_add(args, g_strdup("--no-tcp"));
+    }
+
+    if (passt->has_udp && !passt->udp) {
+        g_ptr_array_add(args, g_strdup("--no-udp"));
+    }
+
+    if (passt->has_icmp && !passt->icmp) {
+        g_ptr_array_add(args, g_strdup("--no-icmp"));
+    }
+
+    if (passt->has_dhcp && !passt->dhcp) {
+        g_ptr_array_add(args, g_strdup("--no-dhcp"));
+    }
+
+    if (passt->has_ndp && !passt->ndp) {
+        g_ptr_array_add(args, g_strdup("--no-ndp"));
+    }
+
+    if (passt->has_dhcpv6 && !passt->dhcpv6) {
+        g_ptr_array_add(args, g_strdup("--no-dhcpv6"));
+    }
+
+    if (passt->has_ra && !passt->ra) {
+        g_ptr_array_add(args, g_strdup("--no-ra"));
+    }
+
+    if (passt->has_freebind && passt->freebind) {
+        g_ptr_array_add(args, g_strdup("--freebind"));
+    }
+
+    if (passt->has_ipv4 && !passt->ipv4) {
+        g_ptr_array_add(args, g_strdup("--ipv6-only"));
+    }
+
+    if (passt->has_ipv6 && !passt->ipv6) {
+        g_ptr_array_add(args, g_strdup("--ipv4-only"));
+    }
+
+    if (passt->has_search && passt->search) {
+        const StringList *list = passt->search;
+        GString *domains = g_string_new(list->value->str);
+
+        list = list->next;
+        while (list) {
+            g_string_append(domains, " ");
+            g_string_append(domains, list->value->str);
+            list = list->next;
+        }
+
+        g_ptr_array_add(args, g_strdup("--search"));
+        g_ptr_array_add(args, g_string_free(domains, FALSE));
+    }
+
+    if (passt->has_tcp_ports && passt->tcp_ports) {
+        const StringList *list = passt->tcp_ports;
+        GString *tcp_ports = g_string_new(list->value->str);
+
+        list = list->next;
+        while (list) {
+            g_string_append(tcp_ports, ",");
+            g_string_append(tcp_ports, list->value->str);
+            list = list->next;
+        }
+
+        g_ptr_array_add(args, g_strdup("--tcp-ports"));
+        g_ptr_array_add(args, g_string_free(tcp_ports, FALSE));
+    }
+
+    if (passt->has_udp_ports && passt->udp_ports) {
+        const StringList *list = passt->udp_ports;
+        GString *udp_ports = g_string_new(list->value->str);
+
+        list = list->next;
+        while (list) {
+            g_string_append(udp_ports, ",");
+            g_string_append(udp_ports, list->value->str);
+            list = list->next;
+        }
+
+        g_ptr_array_add(args, g_strdup("--udp-ports"));
+        g_ptr_array_add(args, g_string_free(udp_ports, FALSE));
+    }
+
+    g_ptr_array_add(args, NULL);
+
+    return args;
+}
+
+int net_init_passt(const Netdev *netdev, const char *name,
+                   NetClientState *peer, Error **errp)
+{
+    g_autoptr(GError) error = NULL;
+    NetClientState *nc;
+    NetPasstState *s;
+    GPtrArray *args;
+    gchar *pidfile;
+    int pidfd;
+
+    assert(netdev->type == NET_CLIENT_DRIVER_PASST);
+
+    pidfd = g_file_open_tmp("passt-XXXXXX.pid", &pidfile, &error);
+    if (pidfd == -1) {
+        error_setg(errp, "Failed to create temporary file: %s", error->message);
+        return -1;
+    }
+    close(pidfd);
+
+    args = net_passt_decode_args(&netdev->u.passt, pidfile, errp);
+    if (args == NULL) {
+        g_free(pidfile);
+        return -1;
+    }
+
+    nc = qemu_new_net_client(&net_passt_info, peer, "passt", name);
+    s = DO_UPCAST(NetPasstState, data.nc, nc);
+
+    s->args = args;
+    s->pidfile = pidfile;
+
+    if (netdev->u.passt.has_vhost_user && netdev->u.passt.vhost_user) {
+        if (net_passt_vhost_user_init(s, errp) == -1) {
+            qemu_del_net_client(nc);
+            return -1;
+        }
+
+        return 0;
+    }
+
+    if (net_passt_stream_start(s, errp) == -1) {
+        qemu_del_net_client(nc);
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/qapi/net.json b/qapi/net.json
index 97ea1839813b..a027d55f9242 100644
--- a/qapi/net.json
+++ b/qapi/net.json
@@ -112,6 +112,122 @@
   'data': {
     'str': 'str' } }
 
+##
+# @NetDevPasstOptions:
+#
+# Unprivileged user-mode network connectivity using passt
+#
+# @quiet: don't print informational messages
+#
+# @debug: be verbose
+#
+# @trace: extra verbose
+#
+# @vhost-user: enable vhost-user
+#
+# @pcap-file: log traffic to pcap file
+#
+# @mtu: assign MTU via DHCP/NDP
+#
+# @address: IPv4 or IPv6 address
+#
+# @netmask: IPv4 mask
+#
+# @mac: source MAC address
+#
+# @gateway: IPv4 or IPv6 address as gateway
+#
+# @interface: interface for addresses and routes
+#
+# @outbound: bind to address as outbound source
+#
+# @outbound-if4: bind to outbound interface for IPv4
+#
+# @outbound-if6: bind to outbound interface for IPv6
+#
+# @dns: IPv4 or IPv6 address as DNS
+#
+# @search: search domains
+#
+# @fqdn: FQDN to configure client with
+#
+# @dhcp-dns: enable/disable DNS list in DHCP/DHCPv6/NDP
+#
+# @dhcp-search: enable/disable list in DHCP/DHCPv6/NDP
+#
+# @map-host-loopback: addresse to refer to host
+#
+# @map-guest-addr: addr to translate to guest's address
+#
+# @dns-forward: forward DNS queries sent to
+#
+# @dns-host: host nameserver to direct queries to
+#
+# @tcp: enable/disable TCP
+#
+# @udp: enable/disable UDP
+#
+# @icmp: enable/disable ICMP
+#
+# @dhcp: enable/disable DHCP
+#
+# @ndp: enable/disable NDP
+#
+# @dhcpv6: enable/disable DHCPv6
+#
+# @ra: enable/disable route advertisements
+#
+# @freebind: bind to any address for forwarding
+#
+# @ipv4: enable/disable IPv4
+#
+# @ipv6: enable/disable IPv6
+#
+# @tcp-ports: TCP ports to forward
+#
+# @udp-ports: UDP ports to forward
+#
+# Since: 10.1
+##
+{ 'struct': 'NetDevPasstOptions',
+  'data': {
+    '*quiet':              'bool',
+    '*debug':              'bool',
+    '*trace':              'bool',
+    '*vhost-user':         'bool',
+    '*pcap-file':          'str',
+    '*mtu':                'int',
+    '*address':            'str',
+    '*netmask':            'str',
+    '*mac':                'str',
+    '*gateway':            'str',
+    '*interface':          'str',
+    '*outbound':           'str',
+    '*outbound-if4':       'str',
+    '*outbound-if6':       'str',
+    '*dns':                'str',
+    '*search':             ['String'],
+    '*fqdn':               'str',
+    '*dhcp-dns':           'bool',
+    '*dhcp-search':        'bool',
+    '*map-host-loopback':  'str',
+    '*map-guest-addr':     'str',
+    '*dns-forward':        'str',
+    '*dns-host':           'str',
+    '*tcp':                'bool',
+    '*udp':                'bool',
+    '*icmp':               'bool',
+    '*dhcp':               'bool',
+    '*ndp':                'bool',
+    '*dhcpv6':             'bool',
+    '*ra':                 'bool',
+    '*freebind':           'bool',
+    '*ipv4':               'bool',
+    '*ipv6':               'bool',
+    '*tcp-ports':          ['String'],
+    '*udp-ports':          ['String'] },
+    'if': 'CONFIG_PASST' }
+
 ##
 # @NetdevUserOptions:
 #
@@ -729,12 +845,15 @@
 #
 # @af-xdp: since 8.2
 #
+# @passt: since 10.1
+#
 # Since: 2.7
 ##
 { 'enum': 'NetClientDriver',
   'data': [ 'none', 'nic', 'user', 'tap', 'l2tpv3', 'socket', 'stream',
             'dgram', 'vde', 'bridge', 'hubport', 'netmap', 'vhost-user',
             'vhost-vdpa',
+            { 'name': 'passt', 'if': 'CONFIG_PASST' },
             { 'name': 'af-xdp', 'if': 'CONFIG_AF_XDP' },
             { 'name': 'vmnet-host', 'if': 'CONFIG_VMNET' },
             { 'name': 'vmnet-shared', 'if': 'CONFIG_VMNET' },
@@ -756,6 +875,8 @@
   'discriminator': 'type',
   'data': {
     'nic':      'NetLegacyNicOptions',
+    'passt':    { 'type': 'NetDevPasstOptions',
+                  'if': 'CONFIG_PASST' },
     'user':     'NetdevUserOptions',
     'tap':      'NetdevTapOptions',
     'l2tpv3':   'NetdevL2TPv3Options',
diff --git a/qemu-options.hx b/qemu-options.hx
index 7eb8e02b4b93..f40f30c11d7b 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -2796,6 +2796,18 @@ DEFHEADING()
 DEFHEADING(Network options:)
 
 DEF("netdev", HAS_ARG, QEMU_OPTION_netdev,
+#ifdef CONFIG_PASST
+    "-netdev passt,id=str[,quiet=on|off][,debug=on|off][,trace=on|off]\n"
+    "          [,vhost-user=on|off][,pcap-file=file][,mtu=mtu]\n"
+    "          [,address=addr][,netmask=mask][,mac=addr][,gateway=addr]\n"
+    "          [,interface=name][,outbound=address][,outbound-if4=name]\n"
+    "          [,outbound-if6=name][,dns=addr][,search=list][,fqdn=name]\n"
+    "          [,dhcp-dns=on|off][,dhcp-search=on|off][,map-host-loopback=addr]\n"
+    "          [,map-guest-addr=addr][,dns-forward=addr][,dns-host=addr]\n"
+    "          [,tcp=on|off][,udp=on|off][,icmp=on|off][,dhcp=on|off]\n"
+    "          [,ndp=on|off][,dhcpv6=on|off][,ra=on|off][,freebind=on|off]\n"
+    "          [,ipv4=on|off][,ipv6=on|off][,tcp-ports=spec][,udp-ports=spec]\n"
+#endif
 #ifdef CONFIG_SLIRP
     "-netdev user,id=str[,ipv4=on|off][,net=addr[/mask]][,host=addr]\n"
     "         [,ipv6=on|off][,ipv6-net=addr[/int]][,ipv6-host=addr]\n"
@@ -2952,6 +2964,9 @@ DEF("netdev", HAS_ARG, QEMU_OPTION_netdev,
     "                configure a hub port on the hub with ID 'n'\n", QEMU_ARCH_ALL)
 DEF("nic", HAS_ARG, QEMU_OPTION_nic,
     "-nic [tap|bridge|"
+#ifdef CONFIG_PASST
+    "passt|"
+#endif
 #ifdef CONFIG_SLIRP
     "user|"
 #endif
@@ -2984,6 +2999,9 @@ DEF("net", HAS_ARG, QEMU_OPTION_net,
     "                configure or create an on-board (or machine default) NIC and\n"
     "                connect it to hub 0 (please use -nic unless you need a hub)\n"
     "-net ["
+#ifdef CONFIG_PASST
+    "passt|"
+#endif
 #ifdef CONFIG_SLIRP
     "user|"
 #endif
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 8/8] net/passt: Implement vhost-user backend support
  2025-06-18  8:39 [PATCH 0/8] net: Add passt netdev backend Laurent Vivier
                   ` (6 preceding siblings ...)
  2025-06-18  8:39 ` [PATCH 7/8] net: Add passt network backend Laurent Vivier
@ 2025-06-18  8:39 ` Laurent Vivier
  2025-06-23  8:03 ` [PATCH 0/8] net: Add passt netdev backend Jason Wang
  8 siblings, 0 replies; 13+ messages in thread
From: Laurent Vivier @ 2025-06-18  8:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Paolo Bonzini, Michael S. Tsirkin, Eric Blake, Stefano Garzarella,
	Stefan Weil, Jason Wang, Philippe Mathieu-Daudé,
	Marc-André Lureau, Dr. David Alan Gilbert, Markus Armbruster,
	Daniel P. Berrangé, Laurent Vivier

This commit adds support for the vhost-user interface to the passt
network backend, enabling high-performance, accelerated networking for
guests using passt.

The passt backend can now operate in a vhost-user mode, where it
communicates with the guest's virtio-net device over a socket pair
using the vhost-user protocol. This offloads the datapath from the
main QEMU loop, significantly improving network performance.

The implementation involves:
- Extending NetPasstState to manage the vhost-user connection
  state, including the chardev frontend and the vhost_net
  instance.
- Adding the full vhost-user connection and event handling logic,
  similar to the standalone net/vhost-user.c client.
- Populating the NetClientInfo structure with implementations for
  all the required vhost callbacks that were recently refactored
  (is_vhost_user, get_vhost_net, save_acked_features, etc).

When the vhost-user=on option is used with -netdev passt, the new
vhost initialization path is taken instead of the standard
stream-based connection.

Signed-off-by: Laurent Vivier <lvivier@redhat.com>
---
 net/passt.c | 288 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 288 insertions(+)

diff --git a/net/passt.c b/net/passt.c
index c3b89e779c65..e4523a5e5617 100644
--- a/net/passt.c
+++ b/net/passt.c
@@ -7,11 +7,15 @@
  */
 #include "qemu/osdep.h"
 #include <glib/gstdio.h>
+#include "qemu/error-report.h"
 #include <gio/gio.h>
 #include "net/net.h"
 #include "clients.h"
 #include "qapi/error.h"
 #include "io/net-listener.h"
+#include "chardev/char-fe.h"
+#include "net/vhost_net.h"
+#include "hw/virtio/vhost-user.h"
 #include "stream_data.h"
 
 typedef struct NetPasstState {
@@ -19,6 +23,15 @@ typedef struct NetPasstState {
     GPtrArray *args;
     gchar *pidfile;
     pid_t pid;
+#ifdef CONFIG_VHOST_USER
+    /* vhost user */
+    VhostUserState *vhost_user;
+    VHostNetState *vhost_net;
+    CharBackend vhost_chr;
+    guint vhost_watch;
+    uint64_t acked_features;
+    bool started;
+#endif
 } NetPasstState;
 
 static int net_passt_stream_start(NetPasstState *s, Error **errp);
@@ -27,6 +40,24 @@ static void net_passt_cleanup(NetClientState *nc)
 {
     NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
 
+#ifdef CONFIG_VHOST_USER
+    if (s->vhost_net) {
+        vhost_net_cleanup(s->vhost_net);
+        g_free(s->vhost_net);
+        s->vhost_net = NULL;
+    }
+    if (s->vhost_watch) {
+        g_source_remove(s->vhost_watch);
+        s->vhost_watch = 0;
+    }
+    qemu_chr_fe_deinit(&s->vhost_chr, true);
+    if (s->vhost_user) {
+        vhost_user_cleanup(s->vhost_user);
+        g_free(s->vhost_user);
+        s->vhost_user = NULL;
+    }
+#endif
+
     kill(s->pid, SIGTERM);
     g_remove(s->pidfile);
     g_free(s->pidfile);
@@ -60,11 +91,110 @@ static gboolean net_passt_send(QIOChannel *ioc, GIOCondition condition,
     return G_SOURCE_CONTINUE;
 }
 
+#ifdef CONFIG_VHOST_USER
+static int passt_set_vnet_endianness(NetClientState *nc, bool enable)
+{
+    assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
+
+    return 0;
+}
+
+static bool passt_has_vnet_hdr(NetClientState *nc)
+{
+    NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
+
+    assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
+
+    return s->vhost_user != NULL;
+}
+
+static bool passt_has_ufo(NetClientState *nc)
+{
+    NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
+
+    assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
+
+    return s->vhost_user != NULL;
+}
+
+static bool passt_check_peer_type(NetClientState *nc, ObjectClass *oc,
+                                             Error **errp)
+{
+    NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
+    const char *driver = object_class_get_name(oc);
+
+    assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
+
+    if (s->vhost_user != NULL) {
+        return true;
+    }
+
+    if (!g_str_has_prefix(driver, "virtio-net-")) {
+        error_setg(errp, "vhost-user requires frontend driver virtio-net-*");
+        return false;
+    }
+
+    return true;
+}
+
+static bool passt_is_vhost_user(NetClientState *nc)
+{
+    NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
+
+    assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
+
+    return s->vhost_user != NULL;
+}
+
+static struct vhost_net *passt_get_vhost_net(NetClientState *nc)
+{
+    NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
+
+    assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
+
+    return s->vhost_net;
+}
+
+static uint64_t passt_get_acked_features(NetClientState *nc)
+{
+    NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
+
+    assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
+
+    return s->acked_features;
+}
+
+static void passt_save_acked_features(NetClientState *nc)
+{
+    NetPasstState *s = DO_UPCAST(NetPasstState, data.nc, nc);
+
+    assert(nc->info->type == NET_CLIENT_DRIVER_PASST);
+
+    if (s->vhost_net) {
+        uint64_t features = vhost_net_get_acked_features(s->vhost_net);
+        if (features) {
+            s->acked_features = features;
+        }
+    }
+}
+#endif
+
 static NetClientInfo net_passt_info = {
     .type = NET_CLIENT_DRIVER_PASST,
     .size = sizeof(NetPasstState),
     .receive = net_passt_receive,
     .cleanup = net_passt_cleanup,
+#ifdef CONFIG_VHOST_USER
+    .has_vnet_hdr = passt_has_vnet_hdr,
+    .has_ufo = passt_has_ufo,
+    .set_vnet_be = passt_set_vnet_endianness,
+    .set_vnet_le = passt_set_vnet_endianness,
+    .check_peer_type = passt_check_peer_type,
+    .is_vhost_user = passt_is_vhost_user,
+    .get_vhost_net = passt_get_vhost_net,
+    .get_acked_features = passt_get_acked_features,
+    .save_acked_features = passt_save_acked_features,
+#endif
 };
 
 static void net_passt_client_connected(QIOTask *task, gpointer opaque)
@@ -163,13 +293,171 @@ static int net_passt_stream_start(NetPasstState *s, Error **errp)
     return 0;
 }
 
+#ifdef CONFIG_VHOST_USER
+static gboolean passt_vhost_user_watch(void *do_not_use, GIOCondition cond,
+                                       void *opaque)
+{
+    NetPasstState *s = opaque;
+
+    qemu_chr_fe_disconnect(&s->vhost_chr);
+
+    return G_SOURCE_CONTINUE;
+}
+
+static void passt_vhost_user_event(void *opaque, QEMUChrEvent event);
+
+static void chr_closed_bh(void *opaque)
+{
+    NetPasstState *s = opaque;
+
+    passt_save_acked_features(&s->data.nc);
+
+    net_client_set_link(&(NetClientState *){ &s->data.nc }, 1, false);
+
+    qemu_chr_fe_set_handlers(&s->vhost_chr, NULL, NULL, passt_vhost_user_event,
+                             NULL, s, NULL, true);
+}
+
+static void passt_vhost_user_stop(NetPasstState *s)
+{
+    passt_save_acked_features(&s->data.nc);
+    vhost_net_cleanup(s->vhost_net);
+}
+
+static int passt_vhost_user_start(NetPasstState *s, VhostUserState *be)
+{
+    struct vhost_net *net = NULL;
+    VhostNetOptions options;
+
+    options.backend_type = VHOST_BACKEND_TYPE_USER;
+    options.net_backend = &s->data.nc;
+    options.opaque = be;
+    options.busyloop_timeout = 0;
+    options.nvqs = 2;
+
+    net = vhost_net_init(&options);
+    if (!net) {
+        error_report("failed to init passt vhost_net");
+        goto err;
+    }
+
+    if (s->vhost_net) {
+        vhost_net_cleanup(s->vhost_net);
+        g_free(s->vhost_net);
+    }
+    s->vhost_net = net;
+
+    return 0;
+err:
+    if (net) {
+        vhost_net_cleanup(net);
+        g_free(net);
+    }
+    passt_vhost_user_stop(s);
+    return -1;
+}
+
+static void passt_vhost_user_event(void *opaque, QEMUChrEvent event)
+{
+    NetPasstState *s = opaque;
+    Error *err = NULL;
+
+    switch (event) {
+    case CHR_EVENT_OPENED:
+        if (passt_vhost_user_start(s, s->vhost_user) < 0) {
+            qemu_chr_fe_disconnect(&s->vhost_chr);
+            return;
+        }
+        s->vhost_watch = qemu_chr_fe_add_watch(&s->vhost_chr, G_IO_HUP,
+                                               passt_vhost_user_watch, s);
+        net_client_set_link(&(NetClientState *){ &s->data.nc }, 1, true);
+        s->started = true;
+        break;
+    case CHR_EVENT_CLOSED:
+        if (s->vhost_watch) {
+            AioContext *ctx = qemu_get_current_aio_context();
+
+            g_source_remove(s->vhost_watch);
+            s->vhost_watch = 0;
+            qemu_chr_fe_set_handlers(&s->vhost_chr, NULL, NULL,  NULL, NULL,
+                                     NULL, NULL, false);
+
+            aio_bh_schedule_oneshot(ctx, chr_closed_bh, s);
+        }
+        break;
+    case CHR_EVENT_BREAK:
+    case CHR_EVENT_MUX_IN:
+    case CHR_EVENT_MUX_OUT:
+        /* Ignore */
+        break;
+    }
+
+    if (err) {
+        error_report_err(err);
+    }
+}
+
 static int net_passt_vhost_user_init(NetPasstState *s, Error **errp)
+{
+    Chardev *chr;
+    int sv[2];
+
+    if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) {
+        error_setg_errno(errp, errno, "socketpair() failed");
+        return -1;
+    }
+
+    /* connect to passt */
+    qemu_set_info_str(&s->data.nc, "connecting to passt");
+
+    /* create chardev */
+
+    chr = CHARDEV(object_new(TYPE_CHARDEV_SOCKET));
+    if (!chr || qemu_chr_add_client(chr, sv[0]) == -1) {
+        object_unref(OBJECT(chr));
+        error_setg(errp, "Failed to make socket chardev");
+        goto err;
+    }
+
+    s->vhost_user = g_new0(struct VhostUserState, 1);
+    if (!qemu_chr_fe_init(&s->vhost_chr, chr, errp) ||
+        !vhost_user_init(s->vhost_user, &s->vhost_chr, errp)) {
+        goto err;
+    }
 
+    /* start passt */
+    if (net_passt_start_daemon(s, sv[1], errp) == -1) {
+        goto err;
+    }
+
+    do {
+        if (qemu_chr_fe_wait_connected(&s->vhost_chr, errp) < 0) {
+            goto err;
+        }
+
+        qemu_chr_fe_set_handlers(&s->vhost_chr, NULL, NULL,
+                                 passt_vhost_user_event, NULL, s, NULL,
+                                 true);
+    } while (!s->started);
+
+    qemu_set_info_str(&s->data.nc, "vhost-user,connected to pid %d", s->pid);
+
+    close(sv[1]);
+    return 0;
+err:
+    close(sv[0]);
+    close(sv[1]);
+
+    return -1;
+}
+#else
+static int net_passt_vhost_user_init(NetPasstState *s, Error **errp)
 {
     error_setg(errp, "vhost-user parameter not yet implemented");
 
     return -1;
 }
+#endif
 
 static GPtrArray *net_passt_decode_args(const NetDevPasstOptions *passt,
                                         gchar *pidfile, Error **errp)
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH 0/8] net: Add passt netdev backend
  2025-06-18  8:39 [PATCH 0/8] net: Add passt netdev backend Laurent Vivier
                   ` (7 preceding siblings ...)
  2025-06-18  8:39 ` [PATCH 8/8] net/passt: Implement vhost-user backend support Laurent Vivier
@ 2025-06-23  8:03 ` Jason Wang
  2025-06-23  8:10   ` Laurent Vivier
  8 siblings, 1 reply; 13+ messages in thread
From: Jason Wang @ 2025-06-23  8:03 UTC (permalink / raw)
  To: Laurent Vivier
  Cc: qemu-devel, Paolo Bonzini, Michael S. Tsirkin, Eric Blake,
	Stefano Garzarella, Stefan Weil, Philippe Mathieu-Daudé,
	Marc-André Lureau, Dr. David Alan Gilbert, Markus Armbruster,
	Daniel P. Berrangé

On Wed, Jun 18, 2025 at 4:39 PM Laurent Vivier <lvivier@redhat.com> wrote:
>
> This series introduces support for passt as a new network backend for
> QEMU.
>
> passt is a modern, unprivileged, user-mode networking solution that
> provides guest connectivity by launching an external helper process. This
> series adds the core backend and integrates it with vhost-user for
> high-performance, accelerated networking.
>
> The series is structured to first improve the general networking code
> before adding the new feature. The first patch extracts from the stream
> backend the functions that will be reused in the passt backend. The
> following patches are a preparatory refactoring to decouple the generic
> vhost layer from specific backend implementations (tap, vhost-user, etc.).
> This is achieved by replacing hardcoded type checks with a callback-based
> system in NetClientInfo, making the vhost infrastructure more modular and
> extensible.
>
> With the refactoring in place, subsequent patches introduce the passt
> backend itself, reusing the generic stream handling logic. The final
> patch adds vhost-user support to passt, which plugs cleanly into the
> newly refactored vhost layer.
>
> Some benchmarks:
>
>  Reference '-net user':
>
>   -net user,hostfwd=tcp::10001-:10001
>
>     iperf3 -c localhost -p 10001  -t 60 -4
>
>     [ ID] Interval           Transfer     Bitrate         Retr
>     [  5]   0.00-60.00  sec  14.2 GBytes  2.03 Gbits/sec    1            sender
>     [  5]   0.00-60.00  sec  14.2 GBytes  2.03 Gbits/sec                  receiver
>
>  New backend '-netdev passt'
>
>   -netdev passt,vhost-user=off,tcp-ports=10001
>
>     iperf3 -c localhost -p 10001  -t 60 -4
>
>     [ ID] Interval           Transfer     Bitrate         Retr
>     [  5]   0.00-60.00  sec  27.1 GBytes  3.88 Gbits/sec    0            sender
>     [  5]   0.00-60.03  sec  27.1 GBytes  3.88 Gbits/sec                  receiver
>
>   -netdev passt,vhost-user=on,tcp-ports=10001
>
>     iperf3 -c localhost -p 10001  -t 60 -4
>
>     [ ID] Interval           Transfer     Bitrate         Retr
>     [  5]   0.00-60.00  sec   224 GBytes  32.1 Gbits/sec    4            sender
>     [  5]   0.00-60.05  sec   224 GBytes  32.0 Gbits/sec                  receiver

Do we have latency numbers of even PPS?

Thanks

>
> Thanks,
> Laurent
>
> Laurent Vivier (8):
>   net: Refactor stream logic for reuse in '-net passt'
>   net: Define net_client_set_link()
>   net: Introduce helper to identify vhost-user clients
>   net: Add get_vhost_net callback to NetClientInfo
>   net: Add get_acked_features callback to NetClientInfo
>   net: Add save_acked_features callback to NetClientInfo
>   net: Add passt network backend
>   net/passt: Implement vhost-user backend support
>
>  hmp-commands.hx          |   3 +
>  hw/net/vhost_net-stub.c  |   1 -
>  hw/net/vhost_net.c       |  89 ++---
>  hw/net/virtio-net.c      |  18 +-
>  include/net/net.h        |  12 +
>  include/net/tap.h        |   3 -
>  include/net/vhost-user.h |  19 --
>  include/net/vhost-vdpa.h |   2 -
>  meson.build              |   6 +
>  meson_options.txt        |   2 +
>  net/clients.h            |   4 +
>  net/hub.c                |   3 +
>  net/meson.build          |   6 +-
>  net/net.c                |  55 ++-
>  net/passt.c              | 718 +++++++++++++++++++++++++++++++++++++++
>  net/stream.c             | 282 ++++-----------
>  net/stream_data.c        | 193 +++++++++++
>  net/stream_data.h        |  31 ++
>  net/tap-win32.c          |   5 -
>  net/tap.c                |  20 +-
>  net/vhost-user-stub.c    |   1 -
>  net/vhost-user.c         |  22 +-
>  net/vhost-vdpa.c         |   4 +-
>  qapi/net.json            | 121 +++++++
>  qemu-options.hx          |  18 +
>  25 files changed, 1293 insertions(+), 345 deletions(-)
>  delete mode 100644 include/net/vhost-user.h
>  create mode 100644 net/passt.c
>  create mode 100644 net/stream_data.c
>  create mode 100644 net/stream_data.h
>
> --
> 2.49.0
>
>



^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 0/8] net: Add passt netdev backend
  2025-06-23  8:03 ` [PATCH 0/8] net: Add passt netdev backend Jason Wang
@ 2025-06-23  8:10   ` Laurent Vivier
  2025-06-24  0:40     ` Jason Wang
  0 siblings, 1 reply; 13+ messages in thread
From: Laurent Vivier @ 2025-06-23  8:10 UTC (permalink / raw)
  To: Jason Wang
  Cc: qemu-devel, Paolo Bonzini, Michael S. Tsirkin, Eric Blake,
	Stefano Garzarella, Stefan Weil, Philippe Mathieu-Daudé,
	Marc-André Lureau, Dr. David Alan Gilbert, Markus Armbruster,
	Daniel P. Berrangé

On 23/06/2025 10:03, Jason Wang wrote:
> On Wed, Jun 18, 2025 at 4:39 PM Laurent Vivier <lvivier@redhat.com> wrote:
>>
>> This series introduces support for passt as a new network backend for
>> QEMU.
>>
>> passt is a modern, unprivileged, user-mode networking solution that
>> provides guest connectivity by launching an external helper process. This
>> series adds the core backend and integrates it with vhost-user for
>> high-performance, accelerated networking.
>>
>> The series is structured to first improve the general networking code
>> before adding the new feature. The first patch extracts from the stream
>> backend the functions that will be reused in the passt backend. The
>> following patches are a preparatory refactoring to decouple the generic
>> vhost layer from specific backend implementations (tap, vhost-user, etc.).
>> This is achieved by replacing hardcoded type checks with a callback-based
>> system in NetClientInfo, making the vhost infrastructure more modular and
>> extensible.
>>
>> With the refactoring in place, subsequent patches introduce the passt
>> backend itself, reusing the generic stream handling logic. The final
>> patch adds vhost-user support to passt, which plugs cleanly into the
>> newly refactored vhost layer.
>>
>> Some benchmarks:
>>
>>   Reference '-net user':
>>
>>    -net user,hostfwd=tcp::10001-:10001
>>
>>      iperf3 -c localhost -p 10001  -t 60 -4
>>
>>      [ ID] Interval           Transfer     Bitrate         Retr
>>      [  5]   0.00-60.00  sec  14.2 GBytes  2.03 Gbits/sec    1            sender
>>      [  5]   0.00-60.00  sec  14.2 GBytes  2.03 Gbits/sec                  receiver
>>
>>   New backend '-netdev passt'
>>
>>    -netdev passt,vhost-user=off,tcp-ports=10001
>>
>>      iperf3 -c localhost -p 10001  -t 60 -4
>>
>>      [ ID] Interval           Transfer     Bitrate         Retr
>>      [  5]   0.00-60.00  sec  27.1 GBytes  3.88 Gbits/sec    0            sender
>>      [  5]   0.00-60.03  sec  27.1 GBytes  3.88 Gbits/sec                  receiver
>>
>>    -netdev passt,vhost-user=on,tcp-ports=10001
>>
>>      iperf3 -c localhost -p 10001  -t 60 -4
>>
>>      [ ID] Interval           Transfer     Bitrate         Retr
>>      [  5]   0.00-60.00  sec   224 GBytes  32.1 Gbits/sec    4            sender
>>      [  5]   0.00-60.05  sec   224 GBytes  32.0 Gbits/sec                  receiver
> 
> Do we have latency numbers of even PPS?

Could you propose tools and tests I can run to have these numbers?

Thanks,
Laurent



^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 0/8] net: Add passt netdev backend
  2025-06-23  8:10   ` Laurent Vivier
@ 2025-06-24  0:40     ` Jason Wang
  2025-06-24  2:57       ` Lei Yang
  0 siblings, 1 reply; 13+ messages in thread
From: Jason Wang @ 2025-06-24  0:40 UTC (permalink / raw)
  To: Laurent Vivier
  Cc: qemu-devel, Paolo Bonzini, Michael S. Tsirkin, Eric Blake,
	Stefano Garzarella, Stefan Weil, Philippe Mathieu-Daudé,
	Marc-André Lureau, Dr. David Alan Gilbert, Markus Armbruster,
	Daniel P. Berrangé

On Mon, Jun 23, 2025 at 4:10 PM Laurent Vivier <lvivier@redhat.com> wrote:
>
> On 23/06/2025 10:03, Jason Wang wrote:
> > On Wed, Jun 18, 2025 at 4:39 PM Laurent Vivier <lvivier@redhat.com> wrote:
> >>
> >> This series introduces support for passt as a new network backend for
> >> QEMU.
> >>
> >> passt is a modern, unprivileged, user-mode networking solution that
> >> provides guest connectivity by launching an external helper process. This
> >> series adds the core backend and integrates it with vhost-user for
> >> high-performance, accelerated networking.
> >>
> >> The series is structured to first improve the general networking code
> >> before adding the new feature. The first patch extracts from the stream
> >> backend the functions that will be reused in the passt backend. The
> >> following patches are a preparatory refactoring to decouple the generic
> >> vhost layer from specific backend implementations (tap, vhost-user, etc.).
> >> This is achieved by replacing hardcoded type checks with a callback-based
> >> system in NetClientInfo, making the vhost infrastructure more modular and
> >> extensible.
> >>
> >> With the refactoring in place, subsequent patches introduce the passt
> >> backend itself, reusing the generic stream handling logic. The final
> >> patch adds vhost-user support to passt, which plugs cleanly into the
> >> newly refactored vhost layer.
> >>
> >> Some benchmarks:
> >>
> >>   Reference '-net user':
> >>
> >>    -net user,hostfwd=tcp::10001-:10001
> >>
> >>      iperf3 -c localhost -p 10001  -t 60 -4
> >>
> >>      [ ID] Interval           Transfer     Bitrate         Retr
> >>      [  5]   0.00-60.00  sec  14.2 GBytes  2.03 Gbits/sec    1            sender
> >>      [  5]   0.00-60.00  sec  14.2 GBytes  2.03 Gbits/sec                  receiver
> >>
> >>   New backend '-netdev passt'
> >>
> >>    -netdev passt,vhost-user=off,tcp-ports=10001
> >>
> >>      iperf3 -c localhost -p 10001  -t 60 -4
> >>
> >>      [ ID] Interval           Transfer     Bitrate         Retr
> >>      [  5]   0.00-60.00  sec  27.1 GBytes  3.88 Gbits/sec    0            sender
> >>      [  5]   0.00-60.03  sec  27.1 GBytes  3.88 Gbits/sec                  receiver
> >>
> >>    -netdev passt,vhost-user=on,tcp-ports=10001
> >>
> >>      iperf3 -c localhost -p 10001  -t 60 -4
> >>
> >>      [ ID] Interval           Transfer     Bitrate         Retr
> >>      [  5]   0.00-60.00  sec   224 GBytes  32.1 Gbits/sec    4            sender
> >>      [  5]   0.00-60.05  sec   224 GBytes  32.0 Gbits/sec                  receiver
> >
> > Do we have latency numbers of even PPS?
>
> Could you propose tools and tests I can run to have these numbers?

For latency, we can use netperf -t TCP_RR.
For PPS, we can use pktgen, kernel source has samples under
samples/pktgen, pktgen_sample03_burst_single_flow.sh could be a good
script, please make sure burst is used in the test to stress the
virtio-net as much as possible.

Thanks


>
> Thanks,
> Laurent
>



^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 0/8] net: Add passt netdev backend
  2025-06-24  0:40     ` Jason Wang
@ 2025-06-24  2:57       ` Lei Yang
  0 siblings, 0 replies; 13+ messages in thread
From: Lei Yang @ 2025-06-24  2:57 UTC (permalink / raw)
  To: Jason Wang
  Cc: Laurent Vivier, qemu-devel, Paolo Bonzini, Michael S. Tsirkin,
	Eric Blake, Stefano Garzarella, Stefan Weil,
	Philippe Mathieu-Daudé, Marc-André Lureau,
	Dr. David Alan Gilbert, Markus Armbruster,
	Daniel P. Berrangé

Because of these code changes the virtio-net files, CI triggers
regression tests for it with virtio-net, everything works fine.

Tested-by: Lei Yang <leiyang@redhat.com>

On Tue, Jun 24, 2025 at 8:41 AM Jason Wang <jasowang@redhat.com> wrote:
>
> On Mon, Jun 23, 2025 at 4:10 PM Laurent Vivier <lvivier@redhat.com> wrote:
> >
> > On 23/06/2025 10:03, Jason Wang wrote:
> > > On Wed, Jun 18, 2025 at 4:39 PM Laurent Vivier <lvivier@redhat.com> wrote:
> > >>
> > >> This series introduces support for passt as a new network backend for
> > >> QEMU.
> > >>
> > >> passt is a modern, unprivileged, user-mode networking solution that
> > >> provides guest connectivity by launching an external helper process. This
> > >> series adds the core backend and integrates it with vhost-user for
> > >> high-performance, accelerated networking.
> > >>
> > >> The series is structured to first improve the general networking code
> > >> before adding the new feature. The first patch extracts from the stream
> > >> backend the functions that will be reused in the passt backend. The
> > >> following patches are a preparatory refactoring to decouple the generic
> > >> vhost layer from specific backend implementations (tap, vhost-user, etc.).
> > >> This is achieved by replacing hardcoded type checks with a callback-based
> > >> system in NetClientInfo, making the vhost infrastructure more modular and
> > >> extensible.
> > >>
> > >> With the refactoring in place, subsequent patches introduce the passt
> > >> backend itself, reusing the generic stream handling logic. The final
> > >> patch adds vhost-user support to passt, which plugs cleanly into the
> > >> newly refactored vhost layer.
> > >>
> > >> Some benchmarks:
> > >>
> > >>   Reference '-net user':
> > >>
> > >>    -net user,hostfwd=tcp::10001-:10001
> > >>
> > >>      iperf3 -c localhost -p 10001  -t 60 -4
> > >>
> > >>      [ ID] Interval           Transfer     Bitrate         Retr
> > >>      [  5]   0.00-60.00  sec  14.2 GBytes  2.03 Gbits/sec    1            sender
> > >>      [  5]   0.00-60.00  sec  14.2 GBytes  2.03 Gbits/sec                  receiver
> > >>
> > >>   New backend '-netdev passt'
> > >>
> > >>    -netdev passt,vhost-user=off,tcp-ports=10001
> > >>
> > >>      iperf3 -c localhost -p 10001  -t 60 -4
> > >>
> > >>      [ ID] Interval           Transfer     Bitrate         Retr
> > >>      [  5]   0.00-60.00  sec  27.1 GBytes  3.88 Gbits/sec    0            sender
> > >>      [  5]   0.00-60.03  sec  27.1 GBytes  3.88 Gbits/sec                  receiver
> > >>
> > >>    -netdev passt,vhost-user=on,tcp-ports=10001
> > >>
> > >>      iperf3 -c localhost -p 10001  -t 60 -4
> > >>
> > >>      [ ID] Interval           Transfer     Bitrate         Retr
> > >>      [  5]   0.00-60.00  sec   224 GBytes  32.1 Gbits/sec    4            sender
> > >>      [  5]   0.00-60.05  sec   224 GBytes  32.0 Gbits/sec                  receiver
> > >
> > > Do we have latency numbers of even PPS?
> >
> > Could you propose tools and tests I can run to have these numbers?
>
> For latency, we can use netperf -t TCP_RR.
> For PPS, we can use pktgen, kernel source has samples under
> samples/pktgen, pktgen_sample03_burst_single_flow.sh could be a good
> script, please make sure burst is used in the test to stress the
> virtio-net as much as possible.
>
> Thanks
>
>
> >
> > Thanks,
> > Laurent
> >
>
>



^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2025-06-24  3:12 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-18  8:39 [PATCH 0/8] net: Add passt netdev backend Laurent Vivier
2025-06-18  8:39 ` [PATCH 1/8] net: Refactor stream logic for reuse in '-net passt' Laurent Vivier
2025-06-18  8:39 ` [PATCH 2/8] net: Define net_client_set_link() Laurent Vivier
2025-06-18  8:39 ` [PATCH 3/8] net: Introduce helper to identify vhost-user clients Laurent Vivier
2025-06-18  8:39 ` [PATCH 4/8] net: Add get_vhost_net callback to NetClientInfo Laurent Vivier
2025-06-18  8:39 ` [PATCH 5/8] net: Add get_acked_features " Laurent Vivier
2025-06-18  8:39 ` [PATCH 6/8] net: Add save_acked_features " Laurent Vivier
2025-06-18  8:39 ` [PATCH 7/8] net: Add passt network backend Laurent Vivier
2025-06-18  8:39 ` [PATCH 8/8] net/passt: Implement vhost-user backend support Laurent Vivier
2025-06-23  8:03 ` [PATCH 0/8] net: Add passt netdev backend Jason Wang
2025-06-23  8:10   ` Laurent Vivier
2025-06-24  0:40     ` Jason Wang
2025-06-24  2:57       ` Lei Yang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).