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


Reply via email to