On 7/13/23 19:10, Laszlo Ersek wrote:
> On QEMU 7.2.0+, if "passt" is available, ask QEMU for passt ("stream")
> rather than SLIRP ("user") networking.
> 
> For this, we need to run passt ourselves. Given that passt daemonizes by
> default, start it with our traditional function guestfs_int_cmd_run(). Ask
> passt to save its PID file, because in case something goes wrong before
> we're completely sure the appliance (i.e. QEMU) is up and running, we'll
> need to kill passt, the *grandchild*, ourselves.
> 
> Pass "--one-off" to passt (same as libvirt). This way, once we have proof
> that QEMU has connected to passt (because the appliance shows signs of
> life), we need not clean up passt ourselves -- once QEMU exits, passt will
> see an EOF on the unix domain socket, and exit as well.
> 
> Passt is way more flexible than SLIRP, and passt normally intends to
> imitate the host environment in the guest as much as possible. This means
> that, when switching from SLIRP to passt, the guest would see changes to
> the following:
> 
> - guest IP address,
> 
> - guest subnet mask,
> 
> - host (= gateway) IP address,
> 
> - host (= gateway) MAC address.
> 
> Extract the SLIRP defaults into the new macros NETWORK_GW_IP and
> NETWORK_GW_MAC, and pass them explicitly to passt. In particular,
> "tests/rsync/test-rsync.sh" fails without setting the host address
> (NETWORK_GW_IP) properly.
> 
> (These artifacts can be verified in the appliance with "virt-rescue
> --network", by running "ip addr", "ip route", and "ip neighbor" at the
> virt-rescue prompt. There are four scenarios: two libguest backends, times
> passt being installed or not installed.)
> 
> Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2184967
> Signed-off-by: Laszlo Ersek <ler...@redhat.com>
> ---
>  lib/guestfs-internal.h |  26 ++++
>  lib/launch-direct.c    | 147 +++++++++++++++++++-
>  2 files changed, 168 insertions(+), 5 deletions(-)
> 
> diff --git a/lib/guestfs-internal.h b/lib/guestfs-internal.h
> index 9ba4d4ad46cf..9f4f800e6d6e 100644
> --- a/lib/guestfs-internal.h
> +++ b/lib/guestfs-internal.h
> @@ -158,6 +158,32 @@ cleanup_mutex_unlock (pthread_mutex_t **ptr)
>  #define NETWORK_ADDRESS "169.254.2.15"
>  #define NETWORK_PREFIX  "16"
>  
> +/* The IP address and the MAC address of the host, as seen from the 
> appliance.
> + *
> + * NETWORK_GW_IP should be the same as NETWORK_ADDRESS, only replacing ".15" 
> in
> + * the rightmost octet with ".2".  NETWORK_GW_MAC cannot be changed.  These
> + * restrictions are a consequence of the following landscape:
> + *
> + * libguestfs backend  userspace network stack  restrictions
> + * ------------------  -----------------------  
> --------------------------------
> + * direct              passt                    None; both NETWORK_GW_IP and
> + *                                              NETWORK_GW_MAC can be set on 
> the
> + *                                              passt command line.
> + *
> + * direct              SLIRP                    SLIRP hard-codes 
> NETWORK_GW_MAC.
> + *
> + * libvirt             passt                    The domain XML does not 
> expose
> + *                                              either knob (RHBZ#2222766), 
> even
> + *                                              though passt could accept 
> both.
> + *
> + * libvirt             SLIRP                    The domain XML does not 
> expose
> + *                                              either knob (RHBZ#2222766), 
> and
> + *                                              SLIRP hard-codes 
> NETWORK_GW_MAC
> + *                                              anyway.
> + */
> +#define NETWORK_GW_IP   "169.254.2.2"
> +#define NETWORK_GW_MAC  "52:56:00:00:00:02"
> +
>  /* Guestfs handle and associated structures. */
>  
>  /* State. */
> diff --git a/lib/launch-direct.c b/lib/launch-direct.c
> index 3f46f0509736..8d6ad025a4e1 100644
> --- a/lib/launch-direct.c
> +++ b/lib/launch-direct.c
> @@ -32,6 +32,7 @@
>  #include <errno.h>
>  #include <fcntl.h>
>  #include <sys/types.h>
> +#include <sys/wait.h>
>  #include <sys/time.h>
>  #include <sys/resource.h>
>  #include <sys/stat.h>
> @@ -48,6 +49,7 @@
>  #include "guestfs-internal.h"
>  #include "guestfs_protocol.h"
>  #include "qemuopts.h"
> +#include "ignore-value.h"
>  
>  /* Per-handle data. */
>  struct backend_direct_data {
> @@ -333,6 +335,110 @@ add_drives (guestfs_h *g, struct backend_direct_data 
> *data,
>    return 0;
>  }
>  
> +/**
> + * Launch passt such that it daemonizes.
> + *
> + * On error, -1 is returned; C<passt_pid> and C<sockpath> are not modified.
> + *
> + * On success, 0 is returned.  C<passt_pid> contains the PID of the passt
> + * background process.  C<sockpath> contains the pathname of the unix domain
> + * socket where passt will accept a single connection.
> + */

Arguably, I should have typed C<-1> and C<0> above; I can fix those up,
upon pushing, in case no more changes were requested.

Laszlo

> +static int
> +launch_passt (guestfs_h *g, long *passt_pid, char (*sockpath)[UNIX_PATH_MAX])
> +{
> +  int rc;
> +  char sockpath_local[sizeof *sockpath];
> +  char *pid_path;
> +  struct command *cmd;
> +  int passt_status;
> +  int passt_exit;
> +  char *pid_str;
> +  long passt_pid_local;
> +  char *endptr;
> +
> +  rc = -1;
> +  if (guestfs_int_create_socketname (g, "passt.sock", &sockpath_local) == -1)
> +    return rc;
> +
> +  pid_path = guestfs_int_make_pid_path (g, "passt");
> +  if (pid_path == NULL)
> +    return rc;
> +
> +  cmd = guestfs_int_new_command (g);
> +  if (cmd == NULL)
> +    goto free_pid_path;
> +
> +  guestfs_int_cmd_add_arg (cmd, "passt");
> +  guestfs_int_cmd_add_arg (cmd, "--one-off");
> +  guestfs_int_cmd_add_arg (cmd, "--socket");
> +  guestfs_int_cmd_add_arg (cmd, sockpath_local);
> +  guestfs_int_cmd_add_arg (cmd, "--pid");
> +  guestfs_int_cmd_add_arg (cmd, pid_path);
> +  guestfs_int_cmd_add_arg (cmd, "--address");
> +  guestfs_int_cmd_add_arg (cmd, NETWORK_ADDRESS);
> +  guestfs_int_cmd_add_arg (cmd, "--netmask");
> +  guestfs_int_cmd_add_arg (cmd, NETWORK_PREFIX);
> +  guestfs_int_cmd_add_arg (cmd, "--mac-addr");
> +  guestfs_int_cmd_add_arg (cmd, NETWORK_GW_MAC);
> +  guestfs_int_cmd_add_arg (cmd, "--gateway");
> +  guestfs_int_cmd_add_arg (cmd, NETWORK_GW_IP);
> +
> +  passt_status = guestfs_int_cmd_run (cmd);
> +  if (passt_status == -1)
> +    /* guestfs_int_cmd_run() reports errors internally, so just bail here */
> +    goto close_cmd;
> +
> +  if (WIFSIGNALED (passt_status)) {
> +    error (g, _("passt was killed with signal %d"), WTERMSIG (passt_status));
> +    goto close_cmd;
> +  }
> +
> +  assert (WIFEXITED (passt_status));
> +  passt_exit = WEXITSTATUS (passt_status);
> +  if (passt_exit != 0) {
> +    error (g, _("passt exited with status %d"), passt_exit);
> +    goto close_cmd;
> +  }
> +
> +  /* At this point passt has forked into the background, dropped privileges, 
> and
> +   * written a PID file.  Due to "--one-off", passt will exit once our QEMU
> +   * appliance disappears (forcibly or cleanly); however, we still need the
> +   * passt PID *temporarily*, so we can kill passt in case we encounter an 
> error
> +   * *before* starting the appliance.
> +   */
> +  if (guestfs_int_read_whole_file (g, pid_path, &pid_str, NULL) == -1)
> +    /* Any error has been reported internally, so just bail.  We can't kill
> +     * passt here because we've failed to get its PID in the first place...
> +     */
> +    goto close_cmd;
> +
> +  errno = 0;
> +  passt_pid_local = strtol (pid_str, &endptr, 10);
> +  if (endptr == pid_str || (*endptr != '\0' && *endptr != '\n') || errno != 
> 0 ||
> +      passt_pid_local <= 1) {
> +    /* Same thing, we can't kill passt just yet. */
> +    error (g, _("failed to parse passt PID from '%s'"), pid_path);
> +    goto free_pid_str;
> +  }
> +
> +  /* We're done. */
> +  *passt_pid = passt_pid_local;
> +  ignore_value (strcpy (*sockpath, sockpath_local));
> +  rc = 0;
> +
> +free_pid_str:
> +  free (pid_str);
> +
> +close_cmd:
> +  guestfs_int_cmd_close (cmd);
> +
> +free_pid_path:
> +  free (pid_path);
> +
> +  return rc;
> +}
> +
>  static int
>  launch_direct (guestfs_h *g, void *datav, const char *arg)
>  {
> @@ -340,6 +446,7 @@ launch_direct (guestfs_h *g, void *datav, const char *arg)
>    struct qemuopts *qopts = NULL;
>    int daemon_accept_sock = -1, console_sock = -1;
>    int r;
> +  long passt_pid = -1;
>    int flags;
>    int sv[2];
>    struct sockaddr_un addr;
> @@ -658,11 +765,30 @@ launch_direct (guestfs_h *g, void *datav, const char 
> *arg)
>  
>    /* Enable user networking. */
>    if (g->enable_network) {
> -    start_list ("-netdev") {
> -      append_list ("user");
> -      append_list ("id=usernet");
> -      append_list ("net=" NETWORK_ADDRESS "/" NETWORK_PREFIX);
> -    } end_list ();
> +    /* If qemu is 7.2.0+ and "passt" is available, ask for passt rather
> +     * than SLIRP.  RHBZ#2184967.
> +     */
> +    if (guestfs_int_version_ge (&data->qemu_version, 7, 2, 0) &&
> +        guestfs_int_passt_runnable (g)) {
> +      char passt_sock[UNIX_PATH_MAX];
> +
> +      if (launch_passt (g, &passt_pid, &passt_sock) == -1)
> +        goto cleanup0;
> +
> +      start_list ("-netdev") {
> +        append_list ("stream");
> +        append_list ("id=usernet");
> +        append_list ("addr.type=unix");
> +        append_list_format ("addr.path=%s", passt_sock);
> +      } end_list ();
> +    }
> +    else {
> +      start_list ("-netdev") {
> +        append_list ("user");
> +        append_list ("id=usernet");
> +        append_list ("net=" NETWORK_ADDRESS "/" NETWORK_PREFIX);
> +      } end_list ();
> +    }
>      start_list ("-device") {
>        append_list (VIRTIO_DEVICE_NAME ("virtio-net"));
>        append_list ("netdev=usernet");
> @@ -893,6 +1019,15 @@ launch_direct (guestfs_h *g, void *datav, const char 
> *arg)
>  
>    debug (g, "appliance is up");
>  
> +  /* From this point onward, even if we fail, QEMU terminating (forcefully or
> +   * gracefully) will cause passt to go away as well.  Note that we can't
> +   * precisely tell whether QEMU managed to open the passt socket before QEMU
> +   * failed.  Therefore, err on the side of killing passt needlessly, rather
> +   * than not killing it when needed -- that's why we re-set "passt_pid" to 
> (-1)
> +   * only this late during QEMU startup verification.
> +   */
> +  passt_pid = -1;
> +
>    /* This is possible in some really strange situations, such as
>     * guestfsd starts up OK but then qemu immediately exits.  Check for
>     * it because the caller is probably expecting to be able to send
> @@ -924,6 +1059,8 @@ launch_direct (guestfs_h *g, void *datav, const char 
> *arg)
>    data->qemu_data = NULL;
>  
>   cleanup0:
> +  if (passt_pid != -1)
> +    kill (passt_pid, SIGTERM);
>    if (qopts != NULL)
>      qemuopts_free (qopts);
>    if (daemon_accept_sock >= 0)
> _______________________________________________
> Libguestfs mailing list
> Libguestfs@redhat.com
> https://listman.redhat.com/mailman/listinfo/libguestfs
> 

_______________________________________________
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs

Reply via email to