* [PATCH v2 00/10] net: Add passt netdev backend
@ 2025-06-18 15:57 Laurent Vivier
2025-06-18 15:57 ` [PATCH v2 01/10] net: Refactor stream logic for reuse in '-net passt' Laurent Vivier
` (9 more replies)
0 siblings, 10 replies; 23+ messages in thread
From: Laurent Vivier @ 2025-06-18 15:57 UTC (permalink / raw)
To: qemu-devel
Cc: Marc-André Lureau, Philippe Mathieu-Daudé,
Daniel P. Berrangé, Markus Armbruster, Stefan Weil,
Stefano Garzarella, Jason Wang, Michael S. Tsirkin,
Dr. David Alan Gilbert, Eric Blake, Paolo Bonzini, 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
v2:
- rebase:
fix conflict with
837b87c4c5ba ("net/stream: skip automatic zero-init of large array")
(why is this needed? A buffer on a stack is normally not initialized...)
- add path parameter to provide path of passt if it is not in PATH
- add 2 patches:
"net: Allow network backends to advertise max TX queue size"
"net: Consolidate vhost feature bits into NetClientInfo"
Thanks,
Laurent
Laurent Vivier (10):
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: Consolidate vhost feature bits into NetClientInfo
net: Add get_acked_features callback to NetClientInfo
net: Add save_acked_features callback to NetClientInfo
net: Allow network backends to advertise max TX queue size
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 | 139 +------
hw/net/virtio-net.c | 18 +-
include/net/net.h | 14 +
include/net/tap.h | 3 -
include/net/vhost-user.h | 19 -
include/net/vhost-vdpa.h | 4 -
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 | 768 +++++++++++++++++++++++++++++++++++++++
net/stream.c | 282 ++++----------
net/stream_data.c | 193 ++++++++++
net/stream_data.h | 31 ++
net/tap-win32.c | 5 -
net/tap.c | 39 +-
net/vhost-user-stub.c | 1 -
net/vhost-user.c | 66 +++-
net/vhost-vdpa.c | 10 +-
qapi/net.json | 124 +++++++
qemu-options.hx | 18 +
25 files changed, 1399 insertions(+), 415 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] 23+ messages in thread
* [PATCH v2 01/10] net: Refactor stream logic for reuse in '-net passt'
2025-06-18 15:57 [PATCH v2 00/10] net: Add passt netdev backend Laurent Vivier
@ 2025-06-18 15:57 ` Laurent Vivier
2025-06-18 15:57 ` [PATCH v2 02/10] net: Define net_client_set_link() Laurent Vivier
` (8 subsequent siblings)
9 siblings, 0 replies; 23+ messages in thread
From: Laurent Vivier @ 2025-06-18 15:57 UTC (permalink / raw)
To: qemu-devel
Cc: Marc-André Lureau, Philippe Mathieu-Daudé,
Daniel P. Berrangé, Markus Armbruster, Stefan Weil,
Stefano Garzarella, Jason Wang, Michael S. Tsirkin,
Dr. David Alan Gilbert, Eric Blake, Paolo Bonzini, 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 6152d2a05e52..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;
- QEMU_UNINITIALIZED 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..5af27e0d1d6a
--- /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;
+ QEMU_UNINITIALIZED 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] 23+ messages in thread
* [PATCH v2 02/10] net: Define net_client_set_link()
2025-06-18 15:57 [PATCH v2 00/10] net: Add passt netdev backend Laurent Vivier
2025-06-18 15:57 ` [PATCH v2 01/10] net: Refactor stream logic for reuse in '-net passt' Laurent Vivier
@ 2025-06-18 15:57 ` Laurent Vivier
2025-06-18 15:57 ` [PATCH v2 03/10] net: Introduce helper to identify vhost-user clients Laurent Vivier
` (7 subsequent siblings)
9 siblings, 0 replies; 23+ messages in thread
From: Laurent Vivier @ 2025-06-18 15:57 UTC (permalink / raw)
To: qemu-devel
Cc: Marc-André Lureau, Philippe Mathieu-Daudé,
Daniel P. Berrangé, Markus Armbruster, Stefan Weil,
Stefano Garzarella, Jason Wang, Michael S. Tsirkin,
Dr. David Alan Gilbert, Eric Blake, Paolo Bonzini, 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] 23+ messages in thread
* [PATCH v2 03/10] net: Introduce helper to identify vhost-user clients
2025-06-18 15:57 [PATCH v2 00/10] net: Add passt netdev backend Laurent Vivier
2025-06-18 15:57 ` [PATCH v2 01/10] net: Refactor stream logic for reuse in '-net passt' Laurent Vivier
2025-06-18 15:57 ` [PATCH v2 02/10] net: Define net_client_set_link() Laurent Vivier
@ 2025-06-18 15:57 ` Laurent Vivier
2025-07-01 1:36 ` Jason Wang
2025-06-18 15:57 ` [PATCH v2 04/10] net: Add get_vhost_net callback to NetClientInfo Laurent Vivier
` (6 subsequent siblings)
9 siblings, 1 reply; 23+ messages in thread
From: Laurent Vivier @ 2025-06-18 15:57 UTC (permalink / raw)
To: qemu-devel
Cc: Marc-André Lureau, Philippe Mathieu-Daudé,
Daniel P. Berrangé, Markus Armbruster, Stefan Weil,
Stefano Garzarella, Jason Wang, Michael S. Tsirkin,
Dr. David Alan Gilbert, Eric Blake, Paolo Bonzini, 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 eb93607b8c76..1367d2b581b1 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] 23+ messages in thread
* [PATCH v2 04/10] net: Add get_vhost_net callback to NetClientInfo
2025-06-18 15:57 [PATCH v2 00/10] net: Add passt netdev backend Laurent Vivier
` (2 preceding siblings ...)
2025-06-18 15:57 ` [PATCH v2 03/10] net: Introduce helper to identify vhost-user clients Laurent Vivier
@ 2025-06-18 15:57 ` Laurent Vivier
2025-06-18 15:57 ` [PATCH v2 05/10] net: Consolidate vhost feature bits into NetClientInfo Laurent Vivier
` (5 subsequent siblings)
9 siblings, 0 replies; 23+ messages in thread
From: Laurent Vivier @ 2025-06-18 15:57 UTC (permalink / raw)
To: qemu-devel
Cc: Marc-André Lureau, Philippe Mathieu-Daudé,
Daniel P. Berrangé, Markus Armbruster, Stefan Weil,
Stefano Garzarella, Jason Wang, Michael S. Tsirkin,
Dr. David Alan Gilbert, Eric Blake, Paolo Bonzini, 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] 23+ messages in thread
* [PATCH v2 05/10] net: Consolidate vhost feature bits into NetClientInfo
2025-06-18 15:57 [PATCH v2 00/10] net: Add passt netdev backend Laurent Vivier
` (3 preceding siblings ...)
2025-06-18 15:57 ` [PATCH v2 04/10] net: Add get_vhost_net callback to NetClientInfo Laurent Vivier
@ 2025-06-18 15:57 ` Laurent Vivier
2025-07-01 1:39 ` Jason Wang
2025-06-18 15:57 ` [PATCH v2 06/10] net: Add get_acked_features callback to NetClientInfo Laurent Vivier
` (4 subsequent siblings)
9 siblings, 1 reply; 23+ messages in thread
From: Laurent Vivier @ 2025-06-18 15:57 UTC (permalink / raw)
To: qemu-devel
Cc: Marc-André Lureau, Philippe Mathieu-Daudé,
Daniel P. Berrangé, Markus Armbruster, Stefan Weil,
Stefano Garzarella, Jason Wang, Michael S. Tsirkin,
Dr. David Alan Gilbert, Eric Blake, Paolo Bonzini, Laurent Vivier
Previously, the vhost_net_get_feature_bits() function in
hw/net/vhost_net.c used a large switch statement to determine
the appropriate feature bits based on the NetClientDriver type.
This created unnecessary coupling between the generic vhost layer
and specific network backends (like TAP, vhost-user, and
vhost-vdpa).
This patch moves the definition of vhost feature bits directly into the
NetClientInfo structure for each relevant network client.
This refactoring centralizes feature bit definitions where they're
needed, making code easier to add new vhost-enabled network backends
in the future without modifying core vhost logic.
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
---
hw/net/vhost_net.c | 80 ++--------------------------------------
include/net/net.h | 1 +
include/net/vhost-vdpa.h | 2 -
net/tap.c | 19 ++++++++++
net/vhost-user.c | 43 +++++++++++++++++++++
net/vhost-vdpa.c | 4 +-
6 files changed, 70 insertions(+), 79 deletions(-)
diff --git a/hw/net/vhost_net.c b/hw/net/vhost_net.c
index 16dadd022e75..3dff819d2dbd 100644
--- a/hw/net/vhost_net.c
+++ b/hw/net/vhost_net.c
@@ -36,86 +36,14 @@
#include "hw/virtio/virtio-bus.h"
#include "linux-headers/linux/vhost.h"
-
-/* Features supported by host kernel. */
-static const int kernel_feature_bits[] = {
- VIRTIO_F_NOTIFY_ON_EMPTY,
- VIRTIO_RING_F_INDIRECT_DESC,
- VIRTIO_RING_F_EVENT_IDX,
- VIRTIO_NET_F_MRG_RXBUF,
- VIRTIO_F_VERSION_1,
- VIRTIO_NET_F_MTU,
- VIRTIO_F_IOMMU_PLATFORM,
- VIRTIO_F_RING_PACKED,
- VIRTIO_F_RING_RESET,
- VIRTIO_F_IN_ORDER,
- VIRTIO_F_NOTIFICATION_DATA,
- VIRTIO_NET_F_RSC_EXT,
- VIRTIO_NET_F_HASH_REPORT,
- VHOST_INVALID_FEATURE_BIT
-};
-
-/* Features supported by others. */
-static const int user_feature_bits[] = {
- VIRTIO_F_NOTIFY_ON_EMPTY,
- VIRTIO_F_NOTIFICATION_DATA,
- VIRTIO_RING_F_INDIRECT_DESC,
- VIRTIO_RING_F_EVENT_IDX,
-
- VIRTIO_F_ANY_LAYOUT,
- VIRTIO_F_VERSION_1,
- VIRTIO_NET_F_CSUM,
- VIRTIO_NET_F_GUEST_CSUM,
- VIRTIO_NET_F_GSO,
- VIRTIO_NET_F_GUEST_TSO4,
- VIRTIO_NET_F_GUEST_TSO6,
- VIRTIO_NET_F_GUEST_ECN,
- VIRTIO_NET_F_GUEST_UFO,
- VIRTIO_NET_F_HOST_TSO4,
- VIRTIO_NET_F_HOST_TSO6,
- VIRTIO_NET_F_HOST_ECN,
- VIRTIO_NET_F_HOST_UFO,
- VIRTIO_NET_F_MRG_RXBUF,
- VIRTIO_NET_F_MTU,
- VIRTIO_F_IOMMU_PLATFORM,
- VIRTIO_F_RING_PACKED,
- VIRTIO_F_RING_RESET,
- VIRTIO_F_IN_ORDER,
- VIRTIO_NET_F_RSS,
- VIRTIO_NET_F_RSC_EXT,
- VIRTIO_NET_F_HASH_REPORT,
- VIRTIO_NET_F_GUEST_USO4,
- VIRTIO_NET_F_GUEST_USO6,
- VIRTIO_NET_F_HOST_USO,
-
- /* This bit implies RARP isn't sent by QEMU out of band */
- VIRTIO_NET_F_GUEST_ANNOUNCE,
-
- VIRTIO_NET_F_MQ,
-
- VHOST_INVALID_FEATURE_BIT
-};
-
static const int *vhost_net_get_feature_bits(struct vhost_net *net)
{
- if (net->nc->info->type == NET_CLIENT_DRIVER_TAP) {
- return kernel_feature_bits;
- }
-
- if (qemu_is_vhost_user(net->nc)) {
- return user_feature_bits;
+ if (net->nc->info->vhost_feature_bits == NULL) {
+ error_report("Feature bits not defined for this type: %d",
+ net->nc->info->type);
}
-#ifdef CONFIG_VHOST_NET_VDPA
- if (net->nc->info->type == NET_CLIENT_DRIVER_VHOST_VDPA) {
- return vdpa_feature_bits;
- }
-#endif
-
- error_report("Feature bits not defined for this type: %d",
- net->nc->info->type);
-
- return 0;
+ return net->nc->info->vhost_feature_bits;
}
uint64_t vhost_net_get_features(struct vhost_net *net, uint64_t features)
diff --git a/include/net/net.h b/include/net/net.h
index 8a62cd6e8aab..dd11be11a39f 100644
--- a/include/net/net.h
+++ b/include/net/net.h
@@ -94,6 +94,7 @@ typedef struct NetClientInfo {
NetAnnounce *announce;
SetSteeringEBPF *set_steering_ebpf;
NetCheckPeerType *check_peer_type;
+ const int *vhost_feature_bits;
IsVHostUser *is_vhost_user;
GetVHostNet *get_vhost_net;
} NetClientInfo;
diff --git a/include/net/vhost-vdpa.h b/include/net/vhost-vdpa.h
index 916ead3793d9..f8d7d6c9045b 100644
--- a/include/net/vhost-vdpa.h
+++ b/include/net/vhost-vdpa.h
@@ -14,6 +14,4 @@
#define TYPE_VHOST_VDPA "vhost-vdpa"
-extern const int vdpa_feature_bits[];
-
#endif /* VHOST_VDPA_H */
diff --git a/net/tap.c b/net/tap.c
index 4beba6d7a784..9e5b1a02d8d3 100644
--- a/net/tap.c
+++ b/net/tap.c
@@ -42,11 +42,29 @@
#include "qemu/error-report.h"
#include "qemu/main-loop.h"
#include "qemu/sockets.h"
+#include "hw/virtio/vhost.h"
#include "net/tap.h"
#include "net/vhost_net.h"
+static const int kernel_feature_bits[] = {
+ VIRTIO_F_NOTIFY_ON_EMPTY,
+ VIRTIO_RING_F_INDIRECT_DESC,
+ VIRTIO_RING_F_EVENT_IDX,
+ VIRTIO_NET_F_MRG_RXBUF,
+ VIRTIO_F_VERSION_1,
+ VIRTIO_NET_F_MTU,
+ VIRTIO_F_IOMMU_PLATFORM,
+ VIRTIO_F_RING_PACKED,
+ VIRTIO_F_RING_RESET,
+ VIRTIO_F_IN_ORDER,
+ VIRTIO_F_NOTIFICATION_DATA,
+ VIRTIO_NET_F_RSC_EXT,
+ VIRTIO_NET_F_HASH_REPORT,
+ VHOST_INVALID_FEATURE_BIT
+};
+
typedef struct TAPState {
NetClientState nc;
int fd;
@@ -360,6 +378,7 @@ static NetClientInfo net_tap_info = {
.set_vnet_be = tap_set_vnet_be,
.set_steering_ebpf = tap_set_steering_ebpf,
.get_vhost_net = tap_get_vhost_net,
+ .vhost_feature_bits = kernel_feature_bits,
};
static TAPState *net_tap_fd_init(NetClientState *peer,
diff --git a/net/vhost-user.c b/net/vhost-user.c
index 2caa7e56f7a0..d8fef801de5a 100644
--- a/net/vhost-user.c
+++ b/net/vhost-user.c
@@ -12,7 +12,9 @@
#include "clients.h"
#include "net/vhost_net.h"
#include "net/vhost-user.h"
+#include "hw/virtio/vhost.h"
#include "hw/virtio/vhost-user.h"
+#include "standard-headers/linux/virtio_net.h"
#include "chardev/char-fe.h"
#include "qapi/error.h"
#include "qapi/qapi-commands-net.h"
@@ -22,6 +24,46 @@
#include "qemu/option.h"
#include "trace.h"
+static const int user_feature_bits[] = {
+ VIRTIO_F_NOTIFY_ON_EMPTY,
+ VIRTIO_F_NOTIFICATION_DATA,
+ VIRTIO_RING_F_INDIRECT_DESC,
+ VIRTIO_RING_F_EVENT_IDX,
+
+ VIRTIO_F_ANY_LAYOUT,
+ VIRTIO_F_VERSION_1,
+ VIRTIO_NET_F_CSUM,
+ VIRTIO_NET_F_GUEST_CSUM,
+ VIRTIO_NET_F_GSO,
+ VIRTIO_NET_F_GUEST_TSO4,
+ VIRTIO_NET_F_GUEST_TSO6,
+ VIRTIO_NET_F_GUEST_ECN,
+ VIRTIO_NET_F_GUEST_UFO,
+ VIRTIO_NET_F_HOST_TSO4,
+ VIRTIO_NET_F_HOST_TSO6,
+ VIRTIO_NET_F_HOST_ECN,
+ VIRTIO_NET_F_HOST_UFO,
+ VIRTIO_NET_F_MRG_RXBUF,
+ VIRTIO_NET_F_MTU,
+ VIRTIO_F_IOMMU_PLATFORM,
+ VIRTIO_F_RING_PACKED,
+ VIRTIO_F_RING_RESET,
+ VIRTIO_F_IN_ORDER,
+ VIRTIO_NET_F_RSS,
+ VIRTIO_NET_F_RSC_EXT,
+ VIRTIO_NET_F_HASH_REPORT,
+ VIRTIO_NET_F_GUEST_USO4,
+ VIRTIO_NET_F_GUEST_USO6,
+ VIRTIO_NET_F_HOST_USO,
+
+ /* This bit implies RARP isn't sent by QEMU out of band */
+ VIRTIO_NET_F_GUEST_ANNOUNCE,
+
+ VIRTIO_NET_F_MQ,
+
+ VHOST_INVALID_FEATURE_BIT
+};
+
typedef struct NetVhostUserState {
NetClientState nc;
CharBackend chr; /* only queue index 0 */
@@ -240,6 +282,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,
+ .vhost_feature_bits = user_feature_bits,
};
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 0b86c917ed68..bd699066a3ab 100644
--- a/net/vhost-vdpa.c
+++ b/net/vhost-vdpa.c
@@ -55,7 +55,7 @@ typedef struct VhostVDPAState {
* with the exception of VHOST_INVALID_FEATURE_BIT,
* which should always be the last entry.
*/
-const int vdpa_feature_bits[] = {
+static const int vdpa_feature_bits[] = {
VIRTIO_F_ANY_LAYOUT,
VIRTIO_F_IOMMU_PLATFORM,
VIRTIO_F_NOTIFY_ON_EMPTY,
@@ -433,6 +433,7 @@ static NetClientInfo net_vhost_vdpa_info = {
.check_peer_type = vhost_vdpa_check_peer_type,
.set_steering_ebpf = vhost_vdpa_set_steering_ebpf,
.get_vhost_net = vhost_vdpa_get_vhost_net,
+ .vhost_feature_bits = vdpa_feature_bits,
};
static int64_t vhost_vdpa_get_vring_group(int device_fd, unsigned vq_index,
@@ -1289,6 +1290,7 @@ static NetClientInfo net_vhost_vdpa_cvq_info = {
.check_peer_type = vhost_vdpa_check_peer_type,
.set_steering_ebpf = vhost_vdpa_set_steering_ebpf,
.get_vhost_net = vhost_vdpa_get_vhost_net,
+ .vhost_feature_bits = vdpa_feature_bits,
};
/*
--
2.49.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v2 06/10] net: Add get_acked_features callback to NetClientInfo
2025-06-18 15:57 [PATCH v2 00/10] net: Add passt netdev backend Laurent Vivier
` (4 preceding siblings ...)
2025-06-18 15:57 ` [PATCH v2 05/10] net: Consolidate vhost feature bits into NetClientInfo Laurent Vivier
@ 2025-06-18 15:57 ` Laurent Vivier
2025-06-18 15:57 ` [PATCH v2 07/10] net: Add save_acked_features " Laurent Vivier
` (3 subsequent siblings)
9 siblings, 0 replies; 23+ messages in thread
From: Laurent Vivier @ 2025-06-18 15:57 UTC (permalink / raw)
To: qemu-devel
Cc: Marc-André Lureau, Philippe Mathieu-Daudé,
Daniel P. Berrangé, Markus Armbruster, Stefan Weil,
Stefano Garzarella, Jason Wang, Michael S. Tsirkin,
Dr. David Alan Gilbert, Eric Blake, Paolo Bonzini, 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 3dff819d2dbd..9cfe41b40b41 100644
--- a/hw/net/vhost_net.c
+++ b/hw/net/vhost_net.c
@@ -245,8 +245,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");
@@ -297,17 +297,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 dd11be11a39f..37dc97a06752 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;
@@ -97,6 +98,7 @@ typedef struct NetClientInfo {
const int *vhost_feature_bits;
IsVHostUser *is_vhost_user;
GetVHostNet *get_vhost_net;
+ GetAckedFeatures *get_acked_features;
} NetClientInfo;
struct NetClientState {
@@ -198,6 +200,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 d8fef801de5a..e590b39503de 100644
--- a/net/vhost-user.c
+++ b/net/vhost-user.c
@@ -81,7 +81,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);
@@ -283,6 +283,7 @@ static NetClientInfo net_vhost_user_info = {
.is_vhost_user = vhost_user_is_vhost_user,
.get_vhost_net = vhost_user_get_vhost_net,
.vhost_feature_bits = user_feature_bits,
+ .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] 23+ messages in thread
* [PATCH v2 07/10] net: Add save_acked_features callback to NetClientInfo
2025-06-18 15:57 [PATCH v2 00/10] net: Add passt netdev backend Laurent Vivier
` (5 preceding siblings ...)
2025-06-18 15:57 ` [PATCH v2 06/10] net: Add get_acked_features callback to NetClientInfo Laurent Vivier
@ 2025-06-18 15:57 ` Laurent Vivier
2025-06-18 15:57 ` [PATCH v2 08/10] net: Allow network backends to advertise max TX queue size Laurent Vivier
` (2 subsequent siblings)
9 siblings, 0 replies; 23+ messages in thread
From: Laurent Vivier @ 2025-06-18 15:57 UTC (permalink / raw)
To: qemu-devel
Cc: Marc-André Lureau, Philippe Mathieu-Daudé,
Daniel P. Berrangé, Markus Armbruster, Stefan Weil,
Stefano Garzarella, Jason Wang, Michael S. Tsirkin,
Dr. David Alan Gilbert, Eric Blake, Paolo Bonzini, 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 9cfe41b40b41..e5890bac8200 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"
@@ -80,11 +79,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 37dc97a06752..179ffee5bd11 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;
@@ -99,6 +100,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 e590b39503de..15609faedb88 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.h"
#include "hw/virtio/vhost-user.h"
#include "standard-headers/linux/virtio_net.h"
@@ -88,7 +87,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;
@@ -284,6 +283,7 @@ static NetClientInfo net_vhost_user_info = {
.get_vhost_net = vhost_user_get_vhost_net,
.vhost_feature_bits = user_feature_bits,
.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] 23+ messages in thread
* [PATCH v2 08/10] net: Allow network backends to advertise max TX queue size
2025-06-18 15:57 [PATCH v2 00/10] net: Add passt netdev backend Laurent Vivier
` (6 preceding siblings ...)
2025-06-18 15:57 ` [PATCH v2 07/10] net: Add save_acked_features " Laurent Vivier
@ 2025-06-18 15:57 ` Laurent Vivier
2025-06-18 15:57 ` [PATCH v2 09/10] net: Add passt network backend Laurent Vivier
2025-06-18 15:57 ` [PATCH v2 10/10] net/passt: Implement vhost-user backend support Laurent Vivier
9 siblings, 0 replies; 23+ messages in thread
From: Laurent Vivier @ 2025-06-18 15:57 UTC (permalink / raw)
To: qemu-devel
Cc: Marc-André Lureau, Philippe Mathieu-Daudé,
Daniel P. Berrangé, Markus Armbruster, Stefan Weil,
Stefano Garzarella, Jason Wang, Michael S. Tsirkin,
Dr. David Alan Gilbert, Eric Blake, Paolo Bonzini, Laurent Vivier
This commit refactors how the maximum transmit queue size for
virtio-net devices is determined, making the mechanism more generic
and extensible.
Previously, virtio_net_max_tx_queue_size() contained hardcoded
checks for specific network backend types (vhost-user and
vhost-vdpa) to determine their supported maximum queue size. This
created direct dependencies and would require modifications for
every new backend that supports variable queue sizes.
To improve flexibility, a new max_tx_queue_size field is added
to the NetClientInfo structure. This allows each network backend
to advertise its supported maximum transmit queue size directly.
This change centralizes the configuration of transmit queue sizes,
simplifying the virtio-net driver and making it easier to integrate
new network backends with diverse queue size capabilities.
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
---
hw/net/virtio-net.c | 16 ++--------------
include/net/net.h | 1 +
net/vhost-user.c | 1 +
net/vhost-vdpa.c | 2 ++
4 files changed, 6 insertions(+), 14 deletions(-)
diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c
index 1367d2b581b1..2468621375e9 100644
--- a/hw/net/virtio-net.c
+++ b/hw/net/virtio-net.c
@@ -671,23 +671,11 @@ static int virtio_net_max_tx_queue_size(VirtIONet *n)
{
NetClientState *peer = n->nic_conf.peers.ncs[0];
- /*
- * Backends other than vhost-user or vhost-vdpa don't support max queue
- * size.
- */
- if (!peer) {
+ if (!peer || !peer->info->max_tx_queue_size) {
return VIRTIO_NET_TX_QUEUE_DEFAULT_SIZE;
}
- if (qemu_is_vhost_user(peer)) {
- return VIRTQUEUE_MAX_SIZE;
- }
-
- if (peer->info->type == NET_CLIENT_DRIVER_VHOST_VDPA) {
- return VIRTQUEUE_MAX_SIZE;
- }
-
- return VIRTIO_NET_TX_QUEUE_DEFAULT_SIZE;
+ return peer->info->max_tx_queue_size;
}
static int peer_attach(VirtIONet *n, int index)
diff --git a/include/net/net.h b/include/net/net.h
index 179ffee5bd11..25dd29e07d7f 100644
--- a/include/net/net.h
+++ b/include/net/net.h
@@ -101,6 +101,7 @@ typedef struct NetClientInfo {
GetVHostNet *get_vhost_net;
GetAckedFeatures *get_acked_features;
SaveAcketFeatures *save_acked_features;
+ int max_tx_queue_size;
} NetClientInfo;
struct NetClientState {
diff --git a/net/vhost-user.c b/net/vhost-user.c
index 15609faedb88..89d216714c13 100644
--- a/net/vhost-user.c
+++ b/net/vhost-user.c
@@ -284,6 +284,7 @@ static NetClientInfo net_vhost_user_info = {
.vhost_feature_bits = user_feature_bits,
.get_acked_features = vhost_user_get_acked_features,
.save_acked_features = vhost_user_save_acked_features,
+ .max_tx_queue_size = VIRTQUEUE_MAX_SIZE,
};
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 bd699066a3ab..4fc5d381ceef 100644
--- a/net/vhost-vdpa.c
+++ b/net/vhost-vdpa.c
@@ -434,6 +434,7 @@ static NetClientInfo net_vhost_vdpa_info = {
.set_steering_ebpf = vhost_vdpa_set_steering_ebpf,
.get_vhost_net = vhost_vdpa_get_vhost_net,
.vhost_feature_bits = vdpa_feature_bits,
+ .max_tx_queue_size = VIRTQUEUE_MAX_SIZE,
};
static int64_t vhost_vdpa_get_vring_group(int device_fd, unsigned vq_index,
@@ -1291,6 +1292,7 @@ static NetClientInfo net_vhost_vdpa_cvq_info = {
.set_steering_ebpf = vhost_vdpa_set_steering_ebpf,
.get_vhost_net = vhost_vdpa_get_vhost_net,
.vhost_feature_bits = vdpa_feature_bits,
+ .max_tx_queue_size = VIRTQUEUE_MAX_SIZE,
};
/*
--
2.49.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v2 09/10] net: Add passt network backend
2025-06-18 15:57 [PATCH v2 00/10] net: Add passt netdev backend Laurent Vivier
` (7 preceding siblings ...)
2025-06-18 15:57 ` [PATCH v2 08/10] net: Allow network backends to advertise max TX queue size Laurent Vivier
@ 2025-06-18 15:57 ` Laurent Vivier
2025-06-24 8:16 ` Markus Armbruster
2025-07-01 1:46 ` Jason Wang
2025-06-18 15:57 ` [PATCH v2 10/10] net/passt: Implement vhost-user backend support Laurent Vivier
9 siblings, 2 replies; 23+ messages in thread
From: Laurent Vivier @ 2025-06-18 15:57 UTC (permalink / raw)
To: qemu-devel
Cc: Marc-André Lureau, Philippe Mathieu-Daudé,
Daniel P. Berrangé, Markus Armbruster, Stefan Weil,
Stefano Garzarella, Jason Wang, Michael S. Tsirkin,
Dr. David Alan Gilbert, Eric Blake, Paolo Bonzini, 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 | 434 ++++++++++++++++++++++++++++++++++++++++++++++
qapi/net.json | 124 +++++++++++++
qemu-options.hx | 18 ++
10 files changed, 601 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..ce194b1e02f0
--- /dev/null
+++ b/net/passt.c
@@ -0,0 +1,434 @@
+/*
+ * 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);
+
+ if (passt->path) {
+ g_ptr_array_add(args, g_strdup(passt->path));
+ } else {
+ 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..76d7654414f7 100644
--- a/qapi/net.json
+++ b/qapi/net.json
@@ -112,6 +112,125 @@
'data': {
'str': 'str' } }
+##
+# @NetDevPasstOptions:
+#
+# Unprivileged user-mode network connectivity using passt
+#
+# @path: path to passt binary
+#
+# @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': {
+ '*path': 'str',
+ '*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 +848,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 +878,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 1f862b19a676..4787f9309c69 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[,path=file][,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] 23+ messages in thread
* [PATCH v2 10/10] net/passt: Implement vhost-user backend support
2025-06-18 15:57 [PATCH v2 00/10] net: Add passt netdev backend Laurent Vivier
` (8 preceding siblings ...)
2025-06-18 15:57 ` [PATCH v2 09/10] net: Add passt network backend Laurent Vivier
@ 2025-06-18 15:57 ` Laurent Vivier
9 siblings, 0 replies; 23+ messages in thread
From: Laurent Vivier @ 2025-06-18 15:57 UTC (permalink / raw)
To: qemu-devel
Cc: Marc-André Lureau, Philippe Mathieu-Daudé,
Daniel P. Berrangé, Markus Armbruster, Stefan Weil,
Stefano Garzarella, Jason Wang, Michael S. Tsirkin,
Dr. David Alan Gilbert, Eric Blake, Paolo Bonzini, 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 | 334 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 334 insertions(+)
diff --git a/net/passt.c b/net/passt.c
index ce194b1e02f0..3c6bc0dfe89f 100644
--- a/net/passt.c
+++ b/net/passt.c
@@ -7,18 +7,75 @@
*/
#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.h"
+#include "hw/virtio/vhost-user.h"
+#include "standard-headers/linux/virtio_net.h"
#include "stream_data.h"
+#ifdef CONFIG_VHOST_USER
+static const int user_feature_bits[] = {
+ VIRTIO_F_NOTIFY_ON_EMPTY,
+ VIRTIO_F_NOTIFICATION_DATA,
+ VIRTIO_RING_F_INDIRECT_DESC,
+ VIRTIO_RING_F_EVENT_IDX,
+
+ VIRTIO_F_ANY_LAYOUT,
+ VIRTIO_F_VERSION_1,
+ VIRTIO_NET_F_CSUM,
+ VIRTIO_NET_F_GUEST_CSUM,
+ VIRTIO_NET_F_GSO,
+ VIRTIO_NET_F_GUEST_TSO4,
+ VIRTIO_NET_F_GUEST_TSO6,
+ VIRTIO_NET_F_GUEST_ECN,
+ VIRTIO_NET_F_GUEST_UFO,
+ VIRTIO_NET_F_HOST_TSO4,
+ VIRTIO_NET_F_HOST_TSO6,
+ VIRTIO_NET_F_HOST_ECN,
+ VIRTIO_NET_F_HOST_UFO,
+ VIRTIO_NET_F_MRG_RXBUF,
+ VIRTIO_NET_F_MTU,
+ VIRTIO_F_IOMMU_PLATFORM,
+ VIRTIO_F_RING_PACKED,
+ VIRTIO_F_RING_RESET,
+ VIRTIO_F_IN_ORDER,
+ VIRTIO_NET_F_RSS,
+ VIRTIO_NET_F_RSC_EXT,
+ VIRTIO_NET_F_HASH_REPORT,
+ VIRTIO_NET_F_GUEST_USO4,
+ VIRTIO_NET_F_GUEST_USO6,
+ VIRTIO_NET_F_HOST_USO,
+
+ /* This bit implies RARP isn't sent by QEMU out of band */
+ VIRTIO_NET_F_GUEST_ANNOUNCE,
+
+ VIRTIO_NET_F_MQ,
+
+ VHOST_INVALID_FEATURE_BIT
+};
+#endif
+
typedef struct NetPasstState {
NetStreamData data;
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 +84,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 +135,112 @@ 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,
+ .vhost_feature_bits = user_feature_bits,
+ .get_acked_features = passt_get_acked_features,
+ .save_acked_features = passt_save_acked_features,
+ .max_tx_queue_size = VIRTQUEUE_MAX_SIZE,
+#endif
};
static void net_passt_client_connected(QIOTask *task, gpointer opaque)
@@ -163,13 +339,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] 23+ messages in thread
* Re: [PATCH v2 09/10] net: Add passt network backend
2025-06-18 15:57 ` [PATCH v2 09/10] net: Add passt network backend Laurent Vivier
@ 2025-06-24 8:16 ` Markus Armbruster
2025-06-24 8:37 ` Laurent Vivier
2025-07-01 1:46 ` Jason Wang
1 sibling, 1 reply; 23+ messages in thread
From: Markus Armbruster @ 2025-06-24 8:16 UTC (permalink / raw)
To: Laurent Vivier
Cc: qemu-devel, Marc-André Lureau, Philippe Mathieu-Daudé,
Daniel P. Berrangé, Stefan Weil, Stefano Garzarella,
Jason Wang, Michael S. Tsirkin, Dr. David Alan Gilbert,
Eric Blake, Paolo Bonzini
Laurent Vivier <lvivier@redhat.com> writes:
> 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>
[...]
> diff --git a/qapi/net.json b/qapi/net.json
> index 97ea1839813b..76d7654414f7 100644
> --- a/qapi/net.json
> +++ b/qapi/net.json
> @@ -112,6 +112,125 @@
> 'data': {
> 'str': 'str' } }
>
> +##
> +# @NetDevPasstOptions:
> +#
> +# Unprivileged user-mode network connectivity using passt
> +#
> +# @path: path to passt binary
I'd prefer a more descriptive name.
Elsewhere in this file, we refer to programs like this:
# @script: script to initialize the interface
#
# @downscript: script to shut down the interface
passt isn't a script, of course.
I don't know, perhaps
# @passt-filename: the passt program to run.
or even
# @passt: Filename of the passt program to run.
> +#
> +# @quiet: don't print informational messages
What does the printing? A peek at the code I snipped suggests this flag
is passed to the passt binary as --quiet. Correct?
> +#
> +# @debug: be verbose
> +#
> +# @trace: extra verbose
Likewise for these two.
> +#
> +# @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
Is there anything in this struct that configures qemu-system-FOO itself,
i.e. isn't just passed to passt?
> +#
> +# Since: 10.1
> +##
> +{ 'struct': 'NetDevPasstOptions',
> + 'data': {
> + '*path': 'str',
> + '*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 +848,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 +878,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 1f862b19a676..4787f9309c69 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[,path=file][,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"
No help here?
> +#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
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 09/10] net: Add passt network backend
2025-06-24 8:16 ` Markus Armbruster
@ 2025-06-24 8:37 ` Laurent Vivier
2025-06-24 11:55 ` Markus Armbruster
0 siblings, 1 reply; 23+ messages in thread
From: Laurent Vivier @ 2025-06-24 8:37 UTC (permalink / raw)
To: Markus Armbruster
Cc: qemu-devel, Marc-André Lureau, Philippe Mathieu-Daudé,
Daniel P. Berrangé, Stefan Weil, Stefano Garzarella,
Jason Wang, Michael S. Tsirkin, Dr. David Alan Gilbert,
Eric Blake, Paolo Bonzini
On 24/06/2025 10:16, Markus Armbruster wrote:
> Laurent Vivier <lvivier@redhat.com> writes:
>
>> 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>
>
> [...]
>
>> diff --git a/qapi/net.json b/qapi/net.json
>> index 97ea1839813b..76d7654414f7 100644
>> --- a/qapi/net.json
>> +++ b/qapi/net.json
>> @@ -112,6 +112,125 @@
>> 'data': {
>> 'str': 'str' } }
>>
>> +##
>> +# @NetDevPasstOptions:
>> +#
>> +# Unprivileged user-mode network connectivity using passt
>> +#
>> +# @path: path to passt binary
>
> I'd prefer a more descriptive name.
>
> Elsewhere in this file, we refer to programs like this:
>
> # @script: script to initialize the interface
> #
> # @downscript: script to shut down the interface
>
> passt isn't a script, of course.
>
> I don't know, perhaps
>
> # @passt-filename: the passt program to run.
>
> or even
>
> # @passt: Filename of the passt program to run.
>
>> +#
>> +# @quiet: don't print informational messages
>
> What does the printing? A peek at the code I snipped suggests this flag
> is passed to the passt binary as --quiet. Correct?
>
>> +#
>> +# @debug: be verbose
>> +#
>> +# @trace: extra verbose
>
> Likewise for these two.
>
>> +#
>> +# @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
>
> Is there anything in this struct that configures qemu-system-FOO itself,
> i.e. isn't just passed to passt?
>
Yes, all parameters are just passed to passt.
Do you think it's better not to add all these parameters to netdev backend but only one
generic containing the passt command line parameters?
Thanks,
Laurent
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 09/10] net: Add passt network backend
2025-06-24 8:37 ` Laurent Vivier
@ 2025-06-24 11:55 ` Markus Armbruster
2025-06-24 12:03 ` Daniel P. Berrangé
0 siblings, 1 reply; 23+ messages in thread
From: Markus Armbruster @ 2025-06-24 11:55 UTC (permalink / raw)
To: Laurent Vivier
Cc: qemu-devel, Marc-André Lureau, Philippe Mathieu-Daudé,
Daniel P. Berrangé, Stefan Weil, Stefano Garzarella,
Jason Wang, Michael S. Tsirkin, Dr. David Alan Gilbert,
Eric Blake, Paolo Bonzini, devel
Laurent Vivier <lvivier@redhat.com> writes:
> On 24/06/2025 10:16, Markus Armbruster wrote:
>> Laurent Vivier <lvivier@redhat.com> writes:
>>
>>> 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>
>>
>> [...]
>>
>>> diff --git a/qapi/net.json b/qapi/net.json
>>> index 97ea1839813b..76d7654414f7 100644
>>> --- a/qapi/net.json
>>> +++ b/qapi/net.json
>>> @@ -112,6 +112,125 @@
>>> 'data': {
>>> 'str': 'str' } }
>>>
>>> +##
>>> +# @NetDevPasstOptions:
>>> +#
>>> +# Unprivileged user-mode network connectivity using passt
>>> +#
>>> +# @path: path to passt binary
>>
>> I'd prefer a more descriptive name.
>>
>> Elsewhere in this file, we refer to programs like this:
>>
>> # @script: script to initialize the interface
>> #
>> # @downscript: script to shut down the interface
>>
>> passt isn't a script, of course.
>>
>> I don't know, perhaps
>>
>> # @passt-filename: the passt program to run.
>>
>> or even
>>
>> # @passt: Filename of the passt program to run.
>>
>>> +#
>>> +# @quiet: don't print informational messages
>>
>> What does the printing? A peek at the code I snipped suggests this flag
>> is passed to the passt binary as --quiet. Correct?
>>
>>> +#
>>> +# @debug: be verbose
>>> +#
>>> +# @trace: extra verbose
>>
>> Likewise for these two.
>>
>>> +#
>>> +# @vhost-user: enable vhost-user
[...]
>>> +# @udp-ports: UDP ports to forward
>>
>> Is there anything in this struct that configures qemu-system-FOO itself,
>> i.e. isn't just passed to passt?
>>
>
> Yes, all parameters are just passed to passt.
>
> Do you think it's better not to add all these parameters to netdev backend but only one
> generic containing the passt command line parameters?
I'm not sure.
Thoughts from libvirt's perspective?
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 09/10] net: Add passt network backend
2025-06-24 11:55 ` Markus Armbruster
@ 2025-06-24 12:03 ` Daniel P. Berrangé
2025-06-24 12:47 ` Laurent Vivier
0 siblings, 1 reply; 23+ messages in thread
From: Daniel P. Berrangé @ 2025-06-24 12:03 UTC (permalink / raw)
To: Markus Armbruster
Cc: Laurent Vivier, qemu-devel, Marc-André Lureau,
Philippe Mathieu-Daudé, Stefan Weil, Stefano Garzarella,
Jason Wang, Michael S. Tsirkin, Dr. David Alan Gilbert,
Eric Blake, Paolo Bonzini, devel
On Tue, Jun 24, 2025 at 01:55:20PM +0200, Markus Armbruster wrote:
> Laurent Vivier <lvivier@redhat.com> writes:
>
> > On 24/06/2025 10:16, Markus Armbruster wrote:
> >> Laurent Vivier <lvivier@redhat.com> writes:
> >>
> >>> 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>
> >>
> >> [...]
> >>
> >>> diff --git a/qapi/net.json b/qapi/net.json
> >>> index 97ea1839813b..76d7654414f7 100644
> >>> --- a/qapi/net.json
> >>> +++ b/qapi/net.json
> >>> @@ -112,6 +112,125 @@
> >>> 'data': {
> >>> 'str': 'str' } }
> >>>
> >>> +##
> >>> +# @NetDevPasstOptions:
> >>> +#
> >>> +# Unprivileged user-mode network connectivity using passt
> >>> +#
> >>> +# @path: path to passt binary
> >>
> >> I'd prefer a more descriptive name.
> >>
> >> Elsewhere in this file, we refer to programs like this:
> >>
> >> # @script: script to initialize the interface
> >> #
> >> # @downscript: script to shut down the interface
> >>
> >> passt isn't a script, of course.
> >>
> >> I don't know, perhaps
> >>
> >> # @passt-filename: the passt program to run.
> >>
> >> or even
> >>
> >> # @passt: Filename of the passt program to run.
> >>
> >>> +#
> >>> +# @quiet: don't print informational messages
> >>
> >> What does the printing? A peek at the code I snipped suggests this flag
> >> is passed to the passt binary as --quiet. Correct?
> >>
> >>> +#
> >>> +# @debug: be verbose
> >>> +#
> >>> +# @trace: extra verbose
> >>
> >> Likewise for these two.
> >>
> >>> +#
> >>> +# @vhost-user: enable vhost-user
>
> [...]
>
> >>> +# @udp-ports: UDP ports to forward
> >>
> >> Is there anything in this struct that configures qemu-system-FOO itself,
> >> i.e. isn't just passed to passt?
> >>
> >
> > Yes, all parameters are just passed to passt.
> >
> > Do you think it's better not to add all these parameters to netdev backend but only one
> > generic containing the passt command line parameters?
>
> I'm not sure.
>
> Thoughts from libvirt's perspective?
We already have passt support in libvirt that leverages the existing
vhost-user netdev backend to connect up QEMU.
I see this backend requires QEMU to be able to spawn the passt binary
itselfm, which is not something libvirt would allow via our security
confinement of QEMU. So that would rule out our ability to consume
this netdev backend, as currently written
Is there anything QEMU can do with this passt netdev, that can't be
done via the vhost-user backend ? ie is this merely syntax sugar to
make it easier for humans launching QEMU, or is there some feature
/ performance benefit ?
With regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 09/10] net: Add passt network backend
2025-06-24 12:03 ` Daniel P. Berrangé
@ 2025-06-24 12:47 ` Laurent Vivier
2025-06-25 6:57 ` Markus Armbruster
0 siblings, 1 reply; 23+ messages in thread
From: Laurent Vivier @ 2025-06-24 12:47 UTC (permalink / raw)
To: Daniel P. Berrangé, Markus Armbruster
Cc: qemu-devel, Marc-André Lureau, Philippe Mathieu-Daudé,
Stefan Weil, Stefano Garzarella, Jason Wang, Michael S. Tsirkin,
Dr. David Alan Gilbert, Eric Blake, Paolo Bonzini, devel
On 24/06/2025 14:03, Daniel P. Berrangé wrote:
> On Tue, Jun 24, 2025 at 01:55:20PM +0200, Markus Armbruster wrote:
>> Laurent Vivier <lvivier@redhat.com> writes:
>>
>>> On 24/06/2025 10:16, Markus Armbruster wrote:
>>>> Laurent Vivier <lvivier@redhat.com> writes:
>>>>
>>>>> 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>
>>>>
>>>> [...]
>>>>
>>>>> diff --git a/qapi/net.json b/qapi/net.json
>>>>> index 97ea1839813b..76d7654414f7 100644
>>>>> --- a/qapi/net.json
>>>>> +++ b/qapi/net.json
>>>>> @@ -112,6 +112,125 @@
>>>>> 'data': {
>>>>> 'str': 'str' } }
>>>>>
>>>>> +##
>>>>> +# @NetDevPasstOptions:
>>>>> +#
>>>>> +# Unprivileged user-mode network connectivity using passt
>>>>> +#
>>>>> +# @path: path to passt binary
>>>>
>>>> I'd prefer a more descriptive name.
>>>>
>>>> Elsewhere in this file, we refer to programs like this:
>>>>
>>>> # @script: script to initialize the interface
>>>> #
>>>> # @downscript: script to shut down the interface
>>>>
>>>> passt isn't a script, of course.
>>>>
>>>> I don't know, perhaps
>>>>
>>>> # @passt-filename: the passt program to run.
>>>>
>>>> or even
>>>>
>>>> # @passt: Filename of the passt program to run.
>>>>
>>>>> +#
>>>>> +# @quiet: don't print informational messages
>>>>
>>>> What does the printing? A peek at the code I snipped suggests this flag
>>>> is passed to the passt binary as --quiet. Correct?
>>>>
>>>>> +#
>>>>> +# @debug: be verbose
>>>>> +#
>>>>> +# @trace: extra verbose
>>>>
>>>> Likewise for these two.
>>>>
>>>>> +#
>>>>> +# @vhost-user: enable vhost-user
>>
>> [...]
>>
>>>>> +# @udp-ports: UDP ports to forward
>>>>
>>>> Is there anything in this struct that configures qemu-system-FOO itself,
>>>> i.e. isn't just passed to passt?
>>>>
>>>
>>> Yes, all parameters are just passed to passt.
>>>
>>> Do you think it's better not to add all these parameters to netdev backend but only one
>>> generic containing the passt command line parameters?
>>
>> I'm not sure.
>>
>> Thoughts from libvirt's perspective?
>
> We already have passt support in libvirt that leverages the existing
> vhost-user netdev backend to connect up QEMU.
>
> I see this backend requires QEMU to be able to spawn the passt binary
> itselfm, which is not something libvirt would allow via our security
> confinement of QEMU. So that would rule out our ability to consume
> this netdev backend, as currently written
>
> Is there anything QEMU can do with this passt netdev, that can't be
> done via the vhost-user backend ? ie is this merely syntax sugar to
> make it easier for humans launching QEMU, or is there some feature
> / performance benefit ?
The idea is only to allow user to run directly QEMU with passt in the same way it's done
with the netdev user. There is no other benefit than the easier interface for humans.
For instance, we want to run '-nic passt' as we can run '-nic user'.
Thanks,
Laurent
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 09/10] net: Add passt network backend
2025-06-24 12:47 ` Laurent Vivier
@ 2025-06-25 6:57 ` Markus Armbruster
2025-06-30 14:22 ` Stefano Brivio
0 siblings, 1 reply; 23+ messages in thread
From: Markus Armbruster @ 2025-06-25 6:57 UTC (permalink / raw)
To: Laurent Vivier
Cc: Daniel P. Berrangé, qemu-devel, Marc-André Lureau,
Philippe Mathieu-Daudé, Stefan Weil, Stefano Garzarella,
Jason Wang, Michael S. Tsirkin, Dr. David Alan Gilbert,
Eric Blake, Paolo Bonzini, devel
Laurent Vivier <lvivier@redhat.com> writes:
> On 24/06/2025 14:03, Daniel P. Berrangé wrote:
>> On Tue, Jun 24, 2025 at 01:55:20PM +0200, Markus Armbruster wrote:
>>> Laurent Vivier <lvivier@redhat.com> writes:
>>>
>>>> On 24/06/2025 10:16, Markus Armbruster wrote:
>>>>> Laurent Vivier <lvivier@redhat.com> writes:
>>>>>
>>>>>> 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>
>>>>>
>>>>> [...]
>>>>>
>>>>>> diff --git a/qapi/net.json b/qapi/net.json
>>>>>> index 97ea1839813b..76d7654414f7 100644
>>>>>> --- a/qapi/net.json
>>>>>> +++ b/qapi/net.json
>>>>>> @@ -112,6 +112,125 @@
>>>>>> 'data': {
>>>>>> 'str': 'str' } }
>>>>>> +##
>>>>>> +# @NetDevPasstOptions:
>>>>>> +#
>>>>>> +# Unprivileged user-mode network connectivity using passt
>>>>>> +#
>>>>>> +# @path: path to passt binary
>>>>>
>>>>> I'd prefer a more descriptive name.
>>>>>
>>>>> Elsewhere in this file, we refer to programs like this:
>>>>>
>>>>> # @script: script to initialize the interface
>>>>> #
>>>>> # @downscript: script to shut down the interface
>>>>>
>>>>> passt isn't a script, of course.
>>>>>
>>>>> I don't know, perhaps
>>>>>
>>>>> # @passt-filename: the passt program to run.
>>>>>
>>>>> or even
>>>>>
>>>>> # @passt: Filename of the passt program to run.
>>>>>
>>>>>> +#
>>>>>> +# @quiet: don't print informational messages
>>>>>
>>>>> What does the printing? A peek at the code I snipped suggests this flag
>>>>> is passed to the passt binary as --quiet. Correct?
>>>>>
>>>>>> +#
>>>>>> +# @debug: be verbose
>>>>>> +#
>>>>>> +# @trace: extra verbose
>>>>>
>>>>> Likewise for these two.
>>>>>
>>>>>> +#
>>>>>> +# @vhost-user: enable vhost-user
>>>
>>> [...]
>>>
>>>>>> +# @udp-ports: UDP ports to forward
>>>>>
>>>>> Is there anything in this struct that configures qemu-system-FOO itself,
>>>>> i.e. isn't just passed to passt?
>>>>>
>>>>
>>>> Yes, all parameters are just passed to passt.
>>>>
>>>> Do you think it's better not to add all these parameters to netdev backend but only one
>>>> generic containing the passt command line parameters?
>>>
>>> I'm not sure.
>>>
>>> Thoughts from libvirt's perspective?
>>
>> We already have passt support in libvirt that leverages the existing
>> vhost-user netdev backend to connect up QEMU.
>>
>> I see this backend requires QEMU to be able to spawn the passt binary
>> itselfm, which is not something libvirt would allow via our security
>> confinement of QEMU. So that would rule out our ability to consume
>> this netdev backend, as currently written
>>
>> Is there anything QEMU can do with this passt netdev, that can't be
>> done via the vhost-user backend ? ie is this merely syntax sugar to
>> make it easier for humans launching QEMU, or is there some feature
>> / performance benefit ?
>
> The idea is only to allow user to run directly QEMU with passt in the same way it's done with the netdev user. There is no other benefit than the easier interface for humans.
>
> For instance, we want to run '-nic passt' as we can run '-nic user'.
Good to know! Put it into the commit message, please.
Hmm, it's good to know for management application developers, too. So
we should also put it into documentation, I guess. Where? Looking...
Oh, there's a section "Using passt as the user mode network stack"in
docs/system/devices/net.rst. That section clearly needs an update for
the new backend.
Back to the design question how to pass configuration via qemu-system to
the passt program with this new backend.
Your patch replicates passt command line options as optional members of
QAPI type NetDevPasstOptions. Any passt options not covered are
inaccessible.
Or rather inaccessible via QMP / HMP / CLI. You can still access them
by pointing @passt to a wrapper script.
This leads us to a possible alternative: make such a wrapper script the
*only* way to configure passt. This is like NetdevTapOptions @script
and @downscript. Mind, I'm not telling you it's a good idea, I'm merely
trying to map the solution space!
Instead of trying to make NetDevPasstOptions complete (so you need to
fall back to a wrapper script only when using a version of passt with
different options), we can limit it to a curated set of options. We'd
need to decide which ones :)
You pointed out yet another alternative: pass through the passt command
line directly. Two obvious ways: a single shell command string to be
passed to system(3), or an array of strings to be passed to execv(3).
system(3) is a terrible idea with untrusted input, but this is trusted
input.
Any other interface ideas?
Since the backend is for human user convenience: which of these ways
would be convenient for human users?
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 09/10] net: Add passt network backend
2025-06-25 6:57 ` Markus Armbruster
@ 2025-06-30 14:22 ` Stefano Brivio
0 siblings, 0 replies; 23+ messages in thread
From: Stefano Brivio @ 2025-06-30 14:22 UTC (permalink / raw)
To: Markus Armbruster via Devel
Cc: Markus Armbruster, Laurent Vivier, qemu-devel,
Marc-André Lureau, Philippe Mathieu-Daudé, Stefan Weil,
Stefano Garzarella, Jason Wang, Michael S. Tsirkin ,
Paolo Bonzini, Dr. David Alan Gilbert , Eric Blake
On Wed, 25 Jun 2025 08:57:27 +0200
Markus Armbruster via Devel <devel@lists.libvirt.org> wrote:
> [...]
>
> Back to the design question how to pass configuration via qemu-system to
> the passt program with this new backend.
>
> Your patch replicates passt command line options as optional members of
> QAPI type NetDevPasstOptions. Any passt options not covered are
> inaccessible.
>
> Or rather inaccessible via QMP / HMP / CLI. You can still access them
> by pointing @passt to a wrapper script.
>
> This leads us to a possible alternative: make such a wrapper script the
> *only* way to configure passt. This is like NetdevTapOptions @script
> and @downscript. Mind, I'm not telling you it's a good idea, I'm merely
> trying to map the solution space!
I'd prefer this idea to having explicit QEMU support (code) for each
passt option, mostly because a. it's a lot of code and b. passt doesn't
completely guarantee backwards compatibility, only to a reasonable
extent (the general idea is to deprecate options first, then to make
them disappear in a couple of years).
Still, I see three main issues with it:
1. you would still need to distribute an example of a wrapper script,
and that will eventually get outdated as well
2. it's complicated and somewhat surprising. You would probably need to
configure different wrapper scripts (or functions in scripts?)
depending on what guest you're running
3. that would be the usual fun with LSMs. What SELinux label would the
script have? Should we define a separate AppArmor policy for it? Or
make it a subprofile...?
> Instead of trying to make NetDevPasstOptions complete (so you need to
> fall back to a wrapper script only when using a version of passt with
> different options), we can limit it to a curated set of options. We'd
> need to decide which ones :)
>
> You pointed out yet another alternative: pass through the passt command
> line directly. Two obvious ways: a single shell command string to be
> passed to system(3), or an array of strings to be passed to execv(3).
...so I'd much prefer this approach (it's actually an alternative I was
suggesting to Laurent in some offline chats earlier). We did the same
in Podman for pasta(1) (same binary as passt(1), different command, for
containers):
https://github.com/containers/common/blob/2d2b7a488601a3557301420fce6acb58d0898b95/libnetwork/pasta/pasta_linux.go#L172
where you would do, say:
podman --net=pasta:--pcap,/tmp/pasta.pcap,--no-dhcpv6,...
This one looks simple, the documentation of options is always updated
and maintained as part of passt's documentation, and there's no need to
change anything in QEMU if we introduce new options or deprecate some.
I would use an array of strings (same as it's done for Podman) rather
than a flat argument for system(3), because:
> system(3) is a terrible idea with untrusted input, but this is trusted
> input.
...even if it's trusted input, a single, long argument with quotes is
more error-prone than separate arguments, I think.
> Any other interface ideas?
>
> Since the backend is for human user convenience: which of these ways
> would be convenient for human users?
As I'm familiar with passt(1) options, passing options through is the
most convenient for me, personally.
I often use passt without libvirt exactly because it's awkward to pass,
say, debugging options (I have to use some horrible /usr/local/bin/passt
wrapper script with a switch case inferring the name of the guest from
the socket path given as argument, and sometimes it's unpractical):
https://issues.redhat.com/browse/RHEL-52281
and it's especially awkward to help users debugging things this way, so
it would be nice if QEMU could make it convenient, instead.
--
Stefano
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 03/10] net: Introduce helper to identify vhost-user clients
2025-06-18 15:57 ` [PATCH v2 03/10] net: Introduce helper to identify vhost-user clients Laurent Vivier
@ 2025-07-01 1:36 ` Jason Wang
0 siblings, 0 replies; 23+ messages in thread
From: Jason Wang @ 2025-07-01 1:36 UTC (permalink / raw)
To: Laurent Vivier
Cc: qemu-devel, Marc-André Lureau, Philippe Mathieu-Daudé,
Daniel P. Berrangé, Markus Armbruster, Stefan Weil,
Stefano Garzarella, Michael S. Tsirkin, Dr. David Alan Gilbert,
Eric Blake, Paolo Bonzini
On Wed, Jun 18, 2025 at 11:57 PM Laurent Vivier <lvivier@redhat.com> wrote:
>
> 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.
I'm not sure it's worth doing the change, for example, it works only
for vhost-user, and the rest are still being done via the enum.
Thanks
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 05/10] net: Consolidate vhost feature bits into NetClientInfo
2025-06-18 15:57 ` [PATCH v2 05/10] net: Consolidate vhost feature bits into NetClientInfo Laurent Vivier
@ 2025-07-01 1:39 ` Jason Wang
0 siblings, 0 replies; 23+ messages in thread
From: Jason Wang @ 2025-07-01 1:39 UTC (permalink / raw)
To: Laurent Vivier
Cc: qemu-devel, Marc-André Lureau, Philippe Mathieu-Daudé,
Daniel P. Berrangé, Markus Armbruster, Stefan Weil,
Stefano Garzarella, Michael S. Tsirkin, Dr. David Alan Gilbert,
Eric Blake, Paolo Bonzini
On Wed, Jun 18, 2025 at 11:57 PM Laurent Vivier <lvivier@redhat.com> wrote:
>
> Previously, the vhost_net_get_feature_bits() function in
> hw/net/vhost_net.c used a large switch statement to determine
> the appropriate feature bits based on the NetClientDriver type.
>
> This created unnecessary coupling between the generic vhost layer
> and specific network backends (like TAP, vhost-user, and
> vhost-vdpa).
>
> This patch moves the definition of vhost feature bits directly into the
> NetClientInfo structure for each relevant network client.
>
> This refactoring centralizes feature bit definitions where they're
> needed, making code easier to add new vhost-enabled network backends
> in the future without modifying core vhost logic.
>
> Signed-off-by: Laurent Vivier <lvivier@redhat.com>
> ---
> hw/net/vhost_net.c | 80 ++--------------------------------------
> include/net/net.h | 1 +
> include/net/vhost-vdpa.h | 2 -
> net/tap.c | 19 ++++++++++
> net/vhost-user.c | 43 +++++++++++++++++++++
> net/vhost-vdpa.c | 4 +-
> 6 files changed, 70 insertions(+), 79 deletions(-)
>
> diff --git a/hw/net/vhost_net.c b/hw/net/vhost_net.c
> index 16dadd022e75..3dff819d2dbd 100644
> --- a/hw/net/vhost_net.c
> +++ b/hw/net/vhost_net.c
> @@ -36,86 +36,14 @@
> #include "hw/virtio/virtio-bus.h"
> #include "linux-headers/linux/vhost.h"
>
> -
> -/* Features supported by host kernel. */
> -static const int kernel_feature_bits[] = {
> - VIRTIO_F_NOTIFY_ON_EMPTY,
> - VIRTIO_RING_F_INDIRECT_DESC,
> - VIRTIO_RING_F_EVENT_IDX,
> - VIRTIO_NET_F_MRG_RXBUF,
> - VIRTIO_F_VERSION_1,
> - VIRTIO_NET_F_MTU,
> - VIRTIO_F_IOMMU_PLATFORM,
> - VIRTIO_F_RING_PACKED,
> - VIRTIO_F_RING_RESET,
> - VIRTIO_F_IN_ORDER,
> - VIRTIO_F_NOTIFICATION_DATA,
> - VIRTIO_NET_F_RSC_EXT,
> - VIRTIO_NET_F_HASH_REPORT,
> - VHOST_INVALID_FEATURE_BIT
> -};
> -
> -/* Features supported by others. */
> -static const int user_feature_bits[] = {
> - VIRTIO_F_NOTIFY_ON_EMPTY,
> - VIRTIO_F_NOTIFICATION_DATA,
> - VIRTIO_RING_F_INDIRECT_DESC,
> - VIRTIO_RING_F_EVENT_IDX,
> -
> - VIRTIO_F_ANY_LAYOUT,
> - VIRTIO_F_VERSION_1,
> - VIRTIO_NET_F_CSUM,
> - VIRTIO_NET_F_GUEST_CSUM,
> - VIRTIO_NET_F_GSO,
> - VIRTIO_NET_F_GUEST_TSO4,
> - VIRTIO_NET_F_GUEST_TSO6,
> - VIRTIO_NET_F_GUEST_ECN,
> - VIRTIO_NET_F_GUEST_UFO,
> - VIRTIO_NET_F_HOST_TSO4,
> - VIRTIO_NET_F_HOST_TSO6,
> - VIRTIO_NET_F_HOST_ECN,
> - VIRTIO_NET_F_HOST_UFO,
> - VIRTIO_NET_F_MRG_RXBUF,
> - VIRTIO_NET_F_MTU,
> - VIRTIO_F_IOMMU_PLATFORM,
> - VIRTIO_F_RING_PACKED,
> - VIRTIO_F_RING_RESET,
> - VIRTIO_F_IN_ORDER,
> - VIRTIO_NET_F_RSS,
> - VIRTIO_NET_F_RSC_EXT,
> - VIRTIO_NET_F_HASH_REPORT,
> - VIRTIO_NET_F_GUEST_USO4,
> - VIRTIO_NET_F_GUEST_USO6,
> - VIRTIO_NET_F_HOST_USO,
> -
> - /* This bit implies RARP isn't sent by QEMU out of band */
> - VIRTIO_NET_F_GUEST_ANNOUNCE,
> -
> - VIRTIO_NET_F_MQ,
> -
> - VHOST_INVALID_FEATURE_BIT
> -};
> -
> static const int *vhost_net_get_feature_bits(struct vhost_net *net)
> {
> - if (net->nc->info->type == NET_CLIENT_DRIVER_TAP) {
> - return kernel_feature_bits;
> - }
> -
> - if (qemu_is_vhost_user(net->nc)) {
> - return user_feature_bits;
> + if (net->nc->info->vhost_feature_bits == NULL) {
> + error_report("Feature bits not defined for this type: %d",
> + net->nc->info->type);
> }
>
> -#ifdef CONFIG_VHOST_NET_VDPA
> - if (net->nc->info->type == NET_CLIENT_DRIVER_VHOST_VDPA) {
> - return vdpa_feature_bits;
> - }
> -#endif
> -
> - error_report("Feature bits not defined for this type: %d",
> - net->nc->info->type);
> -
> - return 0;
> + return net->nc->info->vhost_feature_bits;
> }
>
> uint64_t vhost_net_get_features(struct vhost_net *net, uint64_t features)
> diff --git a/include/net/net.h b/include/net/net.h
> index 8a62cd6e8aab..dd11be11a39f 100644
> --- a/include/net/net.h
> +++ b/include/net/net.h
> @@ -94,6 +94,7 @@ typedef struct NetClientInfo {
> NetAnnounce *announce;
> SetSteeringEBPF *set_steering_ebpf;
> NetCheckPeerType *check_peer_type;
> + const int *vhost_feature_bits;
> IsVHostUser *is_vhost_user;
> GetVHostNet *get_vhost_net;
Assuming we had already had get_vhost_net, it looks to me it's better
to move/reuse the vhost_feature_bits there?
Thanks
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 09/10] net: Add passt network backend
2025-06-18 15:57 ` [PATCH v2 09/10] net: Add passt network backend Laurent Vivier
2025-06-24 8:16 ` Markus Armbruster
@ 2025-07-01 1:46 ` Jason Wang
2025-07-01 13:00 ` Laurent Vivier
1 sibling, 1 reply; 23+ messages in thread
From: Jason Wang @ 2025-07-01 1:46 UTC (permalink / raw)
To: Laurent Vivier
Cc: qemu-devel, Marc-André Lureau, Philippe Mathieu-Daudé,
Daniel P. Berrangé, Markus Armbruster, Stefan Weil,
Stefano Garzarella, Michael S. Tsirkin, Dr. David Alan Gilbert,
Eric Blake, Paolo Bonzini
On Wed, Jun 18, 2025 at 11:58 PM Laurent Vivier <lvivier@redhat.com> wrote:
>
> 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 | 434 ++++++++++++++++++++++++++++++++++++++++++++++
> qapi/net.json | 124 +++++++++++++
> qemu-options.hx | 18 ++
> 10 files changed, 601 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..ce194b1e02f0
> --- /dev/null
> +++ b/net/passt.c
> @@ -0,0 +1,434 @@
> +/*
> + * 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);
I wonder if such launching is a must or at least we should allow
accepting fd from the management layer (e.g in the case that execve()
is restricted)?
> + 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;
> +}
> +
...
> + 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..76d7654414f7 100644
> --- a/qapi/net.json
> +++ b/qapi/net.json
> @@ -112,6 +112,125 @@
> 'data': {
> 'str': 'str' } }
>
> +##
> +# @NetDevPasstOptions:
> +#
> +# Unprivileged user-mode network connectivity using passt
> +#
> +# @path: path to passt binary
> +#
> +# @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': {
> + '*path': 'str',
> + '*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' }
I would like to know the plan to support migration and its
compatibility for passt.
Thanks
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 09/10] net: Add passt network backend
2025-07-01 1:46 ` Jason Wang
@ 2025-07-01 13:00 ` Laurent Vivier
2025-07-03 10:27 ` Stefano Brivio
0 siblings, 1 reply; 23+ messages in thread
From: Laurent Vivier @ 2025-07-01 13:00 UTC (permalink / raw)
To: Jason Wang
Cc: qemu-devel, Marc-André Lureau, Philippe Mathieu-Daudé,
Daniel P. Berrangé, Markus Armbruster, Stefan Weil,
Stefano Garzarella, Michael S. Tsirkin, Dr. David Alan Gilbert,
Eric Blake, Paolo Bonzini
On 01/07/2025 03:46, Jason Wang wrote:
> On Wed, Jun 18, 2025 at 11:58 PM Laurent Vivier <lvivier@redhat.com> wrote:
>>
>> 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 | 434 ++++++++++++++++++++++++++++++++++++++++++++++
>> qapi/net.json | 124 +++++++++++++
>> qemu-options.hx | 18 ++
>> 10 files changed, 601 insertions(+)
>> create mode 100644 net/passt.c
>>
...
>> +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);
>
> I wonder if such launching is a must or at least we should allow
> accepting fd from the management layer (e.g in the case that execve()
> is restricted)?
In this case, the user should use the existing interface: externally start passt and use
"-netdev vhost-user" or '-netdev stream'. It's already managed by libvirt. I think this is
a case we shouldn't manage here.
>> + 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;
>> +}
>> +
>
> ...
>
>> + 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..76d7654414f7 100644
>> --- a/qapi/net.json
>> +++ b/qapi/net.json
>> @@ -112,6 +112,125 @@
>> 'data': {
>> 'str': 'str' } }
>>
>> +##
>> +# @NetDevPasstOptions:
>> +#
>> +# Unprivileged user-mode network connectivity using passt
>> +#
>> +# @path: path to passt binary
>> +#
>> +# @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': {
>> + '*path': 'str',
>> + '*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' }
>
> I would like to know the plan to support migration and its
> compatibility for passt.
As I said, the goal of this series is to be able to use '-nic passt' as we can use '-nic
user'. '-nic user' supports migration but TCP connections are lost.
With this series '-nic passt' supports migration but TCP connections are lost.
But we can improve '-nic passt' because we can migrate TCP connections too, for that we
need to launch passt-repair and to use vhost-user, if we really want to do this it can be
added (with a 'migration=on' parameter?)... but it's also already managed by '-netdev
vhost-user' and passt started manually or by libvirt.
Thanks,
Laurent
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2 09/10] net: Add passt network backend
2025-07-01 13:00 ` Laurent Vivier
@ 2025-07-03 10:27 ` Stefano Brivio
0 siblings, 0 replies; 23+ messages in thread
From: Stefano Brivio @ 2025-07-03 10:27 UTC (permalink / raw)
To: Jason Wang, qemu-devel
Cc: Laurent Vivier, Marc-André Lureau,
Philippe Mathieu-Daudé, Daniel P. Berrangé,
Markus Armbruster, Stefan Weil, Stefano Garzarella,
Michael S. Tsirkin, Dr. David Alan Gilbert, Eric Blake,
Paolo Bonzini
On Tue, 1 Jul 2025 15:00:42 +0200
Laurent Vivier <lvivier@redhat.com> wrote:
> On 01/07/2025 03:46, Jason Wang wrote:
>
> > I would like to know the plan to support migration and its
> > compatibility for passt.
>
> As I said, the goal of this series is to be able to use '-nic passt' as we can use '-nic
> user'. '-nic user' supports migration but TCP connections are lost.
>
> With this series '-nic passt' supports migration but TCP connections are lost.
>
> But we can improve '-nic passt' because we can migrate TCP connections too, for that we
> need to launch passt-repair and to use vhost-user, if we really want to do this it can be
> added (with a 'migration=on' parameter?)... but it's also already managed by '-netdev
> vhost-user' and passt started manually or by libvirt.
For context, as I suppose it's not obvious: passt needs a helper on
both source and target to migrate TCP connections, because it needs the
TCP_REPAIR socket option (same as CRIU) to dump and load connection
parameters, to connect() TCP sockets without a new handshake, and to
close() them without a RST.
That helper is passt-repair(1), and its only purpose is to set and
clear TCP_REPAIR on sockets passed via SCM_RIGHTS.
By the way, at the moment, the only user of passt with a full
integration of the TCP migration capabilities is KubeVirt:
https://github.com/kubevirt/enhancements/blob/main/veps/sig-network/passt-migration-proposal.md
https://github.com/kubevirt/kubevirt/pull/14875
With libvirt, right now, one would need to run passt-repair manually.
Ideally, at some point, libvirt should gain the ability of doing all
that for the user, but I haven't even thought of a reasonable proposal
for how exactly libvirt could do this, yet.
Migration of everything else doesn't need any helper, but "everything
else" is just observed addresses at the moment, that is, passt needs to
know what link-local and global unicast addresses the guest used to
properly NAT traffic in some cases, and these are bits of back-end
state we also transfer.
--
Stefano
^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2025-07-03 10:27 UTC | newest]
Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-18 15:57 [PATCH v2 00/10] net: Add passt netdev backend Laurent Vivier
2025-06-18 15:57 ` [PATCH v2 01/10] net: Refactor stream logic for reuse in '-net passt' Laurent Vivier
2025-06-18 15:57 ` [PATCH v2 02/10] net: Define net_client_set_link() Laurent Vivier
2025-06-18 15:57 ` [PATCH v2 03/10] net: Introduce helper to identify vhost-user clients Laurent Vivier
2025-07-01 1:36 ` Jason Wang
2025-06-18 15:57 ` [PATCH v2 04/10] net: Add get_vhost_net callback to NetClientInfo Laurent Vivier
2025-06-18 15:57 ` [PATCH v2 05/10] net: Consolidate vhost feature bits into NetClientInfo Laurent Vivier
2025-07-01 1:39 ` Jason Wang
2025-06-18 15:57 ` [PATCH v2 06/10] net: Add get_acked_features callback to NetClientInfo Laurent Vivier
2025-06-18 15:57 ` [PATCH v2 07/10] net: Add save_acked_features " Laurent Vivier
2025-06-18 15:57 ` [PATCH v2 08/10] net: Allow network backends to advertise max TX queue size Laurent Vivier
2025-06-18 15:57 ` [PATCH v2 09/10] net: Add passt network backend Laurent Vivier
2025-06-24 8:16 ` Markus Armbruster
2025-06-24 8:37 ` Laurent Vivier
2025-06-24 11:55 ` Markus Armbruster
2025-06-24 12:03 ` Daniel P. Berrangé
2025-06-24 12:47 ` Laurent Vivier
2025-06-25 6:57 ` Markus Armbruster
2025-06-30 14:22 ` Stefano Brivio
2025-07-01 1:46 ` Jason Wang
2025-07-01 13:00 ` Laurent Vivier
2025-07-03 10:27 ` Stefano Brivio
2025-06-18 15:57 ` [PATCH v2 10/10] net/passt: Implement vhost-user backend support Laurent Vivier
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).