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
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
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
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
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
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
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.
Uh, I missed that @path is optional. What's the default? It needs to
be documented.
>>> +#
>>> +# @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.
They're all optional. Default values / behavior need to be documented.
I figure we pass them to passt only when present. This means behavior
when absent is whatever passt does then.
> 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
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?
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 :|
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
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?
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
© 2016 - 2025 Red Hat, Inc.