On Wed, Jun 18, 2025 at 11:58 PM Laurent Vivier <lviv...@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 <lviv...@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