I'm running Linux under QEMU as part of a test suite for a Python script that does some privileged operations. Running a full virtual machine is important to me, because:
- I do not want to require sudo access to run tests
- later it will be easier to run the tests across different distributions and kernel versions
- I'm less likely to break my computer
My approach is to start the QEMU VM with a host port forwarded to SSH on the VM, send and run the script over SSH, run the script, and assert things about the remote system. The annoying thing about this setup is the SSH port, and ports for other network applications.
My question: How can I forward ports in user-mode QEMU without conflicts?
My goal here is to be able to run multiple tests concurrently without any prior setup and without sudo, as mentioned above. User-mode networking in QEMU supports port forwarding, and when I pass 0
for the host port in the hostfwd declaration (hostfwd=tcp:127.0.0.1:0-:22
) the OS allocates one dynamically. Awesome! The problem with that has been getting that port reliably in the parent process.
I'm doing the equivalent of:
#!/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
# ...
The guest OS comes up fine.
Things I have tried:
played a little with QMP, but couldn't find a command that provided the information
considered binding a random port in the parent process with
SO_REUSEPORT
and then specifying it for the child command, but QEMU doesn't use this flag, so it doesn't worknetstat -nalp | grep $child_pid
, but this will not be usable when forwarding multiple portspicking a random unbound port, trying to start qemu with it, and trying again if it fails with a message matching
Could not set up host forwarding rule 'tcp:...'
. This works, but- is more likely to fail with more ports
- may obscure other issues that aren't worth retrying for
It's an OK fallback, but I'm holding out hope for a more straightforward solution.