¿Cómo puedo reenviar puertos en QEMU en modo de usuario sin conflictos?

¿Cómo puedo reenviar puertos en QEMU en modo de usuario sin conflictos?

Estoy ejecutando Linux bajo QEMU como parte de un conjunto de pruebas para un script de Python que realiza algunas operaciones privilegiadas. Ejecutar una máquina virtual completa es importante para mí porque:

  1. No quiero requerir acceso sudo para ejecutar pruebas
  2. Más adelante será más fácil ejecutar las pruebas en diferentes distribuciones y versiones del kernel.
  3. Es menos probable que se rompa mi computadora

Mi enfoque es iniciar la máquina virtual QEMU con un puerto de host reenviado a SSH en la máquina virtual, enviar y ejecutar el script a través de SSH, ejecutar el script y afirmar cosas sobre el sistema remoto. Lo molesto de esta configuración es el puerto SSH y los puertos para otras aplicaciones de red.

Mi pregunta:¿Cómo puedo reenviar puertos en QEMU en modo de usuario sin conflictos?

Mi objetivo aquí es poder ejecutar múltiples pruebas simultáneamente sin ninguna configuración previa y sin sudo, como se mencionó anteriormente. La red en modo de usuario en QEMU admite el reenvío de puertos, y cuando paso 0por el puerto del host en la declaración hostfwd ( hostfwd=tcp:127.0.0.1:0-:22), el sistema operativo asigna uno dinámicamente. ¡Impresionante! El problema con esto ha sido obtener ese puerto de manera confiable en el proceso principal.

Estoy haciendo el equivalente a:

#!/bin/sh
set -e

cd "$(mktemp -d)"

# Download cloud image.
image_base_url="https://cloud-images.ubuntu.com/releases/18.04/release/"
image_name="ubuntu-18.04-server-cloudimg-amd64.img"
image_url="$image_base_url$image_name"
curl --location -o linux.img $image_url

# Create SSH key.
ssh_key_path="$PWD/id_rsa"
ssh-keygen -t rsa -b 4096 -N "" -f "$ssh_key_path"

# Create cloud-init payload.
cat <<EOF > user_data
#cloud-config
password: ubuntu
chpasswd: { expire: False }
ssh_pwauth: True
ssh_authorized_keys:
- $(cat "${ssh_key_path}.pub")
EOF
cloud-localds user_data.img user_data

# Socket path for QMP.
qmp_socket="$PWD/qmp.sock"

# Start VM.
qemu-system-x86_64 \
    -drive file=linux.img,format=qcow2 \
    -drive file=user_data.img,format=raw \
    -netdev user,id=net0,hostfwd=tcp:127.0.0.1:0-:22 \
    -device rtl8139,netdev=net0 \
    -enable-kvm \
    -m 2G \
    -serial mon:stdio \
    -nographic \
    -smp 2 \
    -snapshot \
    -qmp unix:$qmp_socket,server,nowait \
    & \
;

# wait a bit, then test stuff here
# ...

El sistema operativo invitado funciona bien.

Cosas que he probado:

  • jugó un poco conMPC, pero no pude encontrar un comando que proporcionara la información

  • Se consideró vincular un puerto aleatorio en el proceso principal SO_REUSEPORTy luego especificarlo para el comando secundario, pero QEMU no usa este indicador, por lo que no funciona.

  • netstat -nalp | grep $child_pid, pero esto no se podrá utilizar al reenviar múltiples puertos

  • elegir un puerto independiente aleatorio, intentar iniciar qemu con él y volver a intentarlo si falla con un mensaje que coincida Could not set up host forwarding rule 'tcp:...'. Esto funciona, pero

    1. es más probable que falle con más puertos
    2. puede ocultar otros problemas por los que no vale la pena volver a intentarlo

    Es una alternativa aceptable, pero tengo la esperanza de una solución más sencilla.

Respuesta1

Después de investigar un poco más: QEMU expone la información a través del info usernetcomando. Así es como se ve el resultado:

(qemu) info usernet
VLAN -1 (net0):
  Protocol[State]    FD  Source Address  Port   Dest. Address  Port RecvQ SendQ
  TCP[HOST_FORWARD]  13       127.0.0.1 38117       10.0.2.15    22     0     0
  UDP[236 sec]       24       10.0.2.15 35061   91.189.89.198   123     0     0
  UDP[204 sec]       26       10.0.2.15 60630   91.189.89.198   123     0     0

No está disponible a través de QMP, pero se envió un parche para agregar un equivalentequery-usernet aquíhace tiempo.

En mi caso, es sencillo exponer el monitor usando -monitor unix:$PWD/mon.sock,server,nowait, conectarlo, enviar el comando, leer el resultado y analizarlo para obtener los valores deseados.

En resumen, para usar QEMU para probar aplicaciones de red sin requerir sudo u otros requisitos previos (aparte del propio QEMU) y evitar conflictos de puertos en el host:

  1. Para cada aplicación, cree una asignación de puerto a través de la línea de comandos ( -netdev) o monitor ( netdev_add), proporcionando 0el puerto del host.
  2. Exponer monitorcomo un socket Unix para la aplicación consumidora
  3. Ejecute info userneta través del socket y extraiga el puerto de origen para cada asignación, que es el puerto real asignado por el sistema operativo.

información relacionada