This page looks best with JavaScript enabled

Qemu under FreeBSD

 ·  🎃 kr0m

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