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 work -
netstat -nalp | grep $child_pid
, but this will not be usable when forwarding multiple ports -
picking 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.
Best Answer
After a little more research: QEMU exposes the information via the
info usernet
command. Here's what the output looks like:It's not available via QMP, but there was a patch submitted to add an equivalent
query-usernet
here some time ago.In my case, it is straightforward to expose the monitor using
-monitor unix:$PWD/mon.sock,server,nowait
, connect, send the command, read the output, and parse it for the desired values.So in summary, to use QEMU to test network applications without requiring sudo or other prereqs (other than QEMU itself), and avoiding port conflicts on the host:
-netdev
) or monitor (netdev_add
), providing0
for the host portmonitor
as a unix socket for the consuming applicationinfo usernet
via the socket, and extract the Source Port for each mapping, which is the actual port allocated by the OS