It’s well known that one of the strengths of FreeBSD is its virtualization system using
jails
, but there are times when we need to install a non-FreeBSD system, such as Windows or Linux.
In such cases, one usually opts for a
Bhyve-based manager
, but if the machine is very old (in my case: IntelCore2Duo P9500/2.53GHz) and lacks the required virtualization extensions at the CPU level, Bhyve will not work — the only option left is Qemu.
Qemu is much slower because it emulates an entire processor without any hardware assistance, and depending on what you intend to use it for, this may or may not be viable.
However, it’s not all bad — Qemu allows delegating a single USB port to a virtual machine, something that BHyve could not do unless
passing through an entire PCI card
.
Installing Qemu:
Install the software:
pkg install qemu
We’ll run tests using Debian, so let’s download the ISO:
wget https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-13.1.0-amd64-netinst.iso
Create the hard disk:
mkdir qemu-vms
qemu-img create -f qcow2 /root/qemu-vms/debian13.img 50G
Boot the virtual machine from the CD-ROM:
qemu-system-x86_64 -m 2048 -hda /root/qemu-vms/debian13.img -cdrom debian-13.1.0-amd64-netinst.iso -boot d -vnc :0 -vga std
If the parent server filters traffic using
PF
, we need to add an ACL to allow access to the VM’s VNC/SSH ports and additional rules if the VM will provide other services:
vi /etc/pf.conf
# VNC Qemu installation:
pass in proto tcp from 192.168.XX.XX to any port 5900
# SSH Qemu:
pass in proto tcp to 192.168.XX.XX port 2222
Access the VNC:
vncviewer 192.168.YY.YY:5900
In my case, it’s necessary to pass through a USB port to the virtual machine, so let’s identify the port:
usbconfig
ugen1.3: <RTL2838 DVB-T Realtek Semiconductor Corp.> at usbus1, cfg=0 md=HOST spd=HIGH (480Mbps) pwr=ON (500mA)
Detach the USB from the host OS before assigning it to the VM:
usbconfig -u 1 -a 3 detach_kernel_driver
Now pass the USB port — by default, Qemu uses SLIRP (NAT internal network), so we must forward the ports we need:
qemu-system-x86_64 \
-accel tcg,thread=multi \
-cpu max \
-smp 2 \
-m 2048 \
-drive file=/root/qemu-vms/debian13.img,if=virtio,cache=writeback \
-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8073-:8073 \
-device virtio-net,netdev=net0 \
-device qemu-xhci,id=xhci \
-device usb-host,hostbus=1,hostaddr=3 \
-vnc :0 -vga std \
-daemonize
Access the VM via SSH:
ssh kr0m@192.168.YY.YY -p2222
Where we can verify that the USB port was successfully passed through:
lsusb
Bus 001 Device 002: ID 0bda:2838 Realtek Semiconductor Corp. RTL2838 DVB-T
To stop the virtual machine, halt is not enough; I had to use systemctl poweroff.
Every time we create a new virtual machine, we must make sure not to reuse the same SSH or VNC ports. If we do, we’ll see errors like:
qemu-system-x86_64: -netdev user,id=net0,hostfwd=tcp::2222-:22: Could not set up host forwarding rule 'tcp::2222-:22'
qemu-system-x86_64: -vnc :0: Failed to find an available port: Address already in use
An example using different ports:
-netdev user,id=net0,hostfwd=tcp::2223-:22 --> ssh kr0m@192.168.YY.YY -p2223
-vnc :1 -vga std --> vncviewer 192.168.YY.YY:5901
If it’s a virtual machine you’ll use regularly, the best way to manage it is through an RC script:
vi /usr/local/etc/rc.d/qemuDebian
#!/bin/sh
#
# PROVIDE: qemuDebian
# REQUIRE: DAEMON
# KEYWORD: shutdown
. /etc/rc.subr
name=qemuDebian
rcvar=qemuDebian_enable
command="/usr/local/bin/qemu-system-x86_64"
command_args="\
-accel tcg,thread=multi \
-cpu max \
-smp 2 \
-m 2048 \
-drive file=/root/qemu-vms/debian13.img,if=virtio,cache=writeback \
-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8073-:8073 \
-device virtio-net,netdev=net0 \
-device qemu-xhci,id=xhci \
-device usb-host,hostbus=1,hostaddr=3 \
-vnc :0 -vga std"
start_cmd="${name}_start"
stop_cmd="${name}_stop"
status_cmd="${name}_status"
pidfile="/var/run/${name}.pid"
qemuDebian_start(){
echo "Starting service: ${name}."
/usr/sbin/daemon -c -f -p ${pidfile} ${command} ${command_args}
}
qemuDebian_stop(){
if [ -f ${pidfile} ]; then
echo "Stopping service: ${name}"
kill -s TERM $(cat ${pidfile})
sleep 3
else
echo "It appears ${name} is not running."
fi
}
qemuDebian_status(){
if [ -f ${pidfile} ]; then
echo "${name} running with PID: $(cat ${pidfile})"
else
echo "It appears ${name} is not running."
fi
}
load_rc_config $name
run_rc_command "$1"
NOTE: Since we now launch Qemu via daemon, the -daemonize parameter is no longer needed.
Set permissions:
chmod 555 /usr/local/etc/rc.d/qemuDebian
Enable the service and start it:
sysrc qemuDebian_enable="YES"
service qemuDebian start
Check that it started:
service qemuDebian status
Tips:
We can view an htop filtered for the Qemu processes, but note that if new VMs are launched, we must rerun the command to refresh the list:
htop -p $(pgrep qemu-system | tr '\n' ',' | sed 's/,$//')
We can get disk information as follows:
qemu-img info /root/qemu-vms/debian13.img
image: /root/qemu-vms/debian13.img
file format: qcow2
virtual size: 50 GiB (53687091200 bytes)
disk size: 2.21 GiB
cluster_size: 65536
Format specific information:
compat: 1.1
compression type: zlib
lazy refcounts: false
refcount bits: 16
corrupt: false
extended l2: false
Child node '/file':
filename: /root/qemu-vms/debian13.img
protocol type: file
file length: 4.77 GiB (5116657664 bytes)
disk size: 2.21 GiB
As we can see, the information matches:
du -h /root/qemu-vms/debian13.img
2.2G /root/qemu-vms/debian13.img
Every time we create a new virtual machine, we must make sure not to reuse the same SSH or VNC ports. If we do, we’ll see these messages:
qemu-system-x86_64: -netdev user,id=net0,hostfwd=tcp::2222-:22: Could not set up host forwarding rule 'tcp::2222-:22'
qemu-system-x86_64: -vnc :0: Failed to find an available port: Address already in use
An example using different ports would be:
-netdev user,id=net0,hostfwd=tcp::2223-:22 --> ssh kr0m@192.168.YY.YY -p2223
-vnc :1 -vga std --> vncviewer 192.168.YY.YY:5901