Skip to content

Instantly share code, notes, and snippets.

@niklas88
Last active May 31, 2025 21:02
Show Gist options
  • Save niklas88/f624f53cc4e425ba0409236445825a36 to your computer and use it in GitHub Desktop.
Save niklas88/f624f53cc4e425ba0409236445825a36 to your computer and use it in GitHub Desktop.
QEMU + MacVTAP with Systemd Unit Files on Arch Linux

QEMU + MacVTAP with Systemd Unit Files

Below I list a launch script which creates the MacVATP tun file as root and then drops privileges to the qemu user. This user thus needs access to the image file passed as first parameter. This is /var/vms/chimera.img in the example systemd unit. Also this launch script makes QEMU create unix sockets for the monitor and console streams. This way socat can be used to access e.g. the guest console with the following command:

socat STDIO,cfmakeraw UNIX:/var/vms/chimera.img.console.sock

The launch script is saved in /usr/local/bin/launch_vm:

#!/usr/bin/env zsh
# Note currently the image is assumed to be of raw format
#
# Useful options to add:
# 	-cdrom foo.iso \
#	-nic user,model=virtio-net-pci,hostfwd=tcp::2222-:22 \
if [ "$#" -ne 3 ]; then
    echo "Usage: $0 <path_to_image> <mac_address> <lower_netdev>"
    exit 1
fi

img="$1"
mac_address="$2"
lower_netdev="$3"
tap_name="${img:t:r}_macvtap"

user="qemu"
img_format="raw"
ovmf_vars="${img}_OVMF_VARS.fd"

ip link add link "${lower_netdev}" name "${tap_name}" address "${mac_address}" type macvtap mode bridge
ip link set "${tap_name}" up

tap_index=$(< /sys/class/net/$tap_name/ifindex)
tap_dev=/dev/tap"$tap_index"
tap_mac=$(< /sys/class/net/$tap_name/address)
exec {tap_fd}<>"$tap_dev"

if [ ! -f "${ovmf_vars}" ]; then
	cp /usr/share/edk2/x64/OVMF_VARS.4m.fd "${ovmf_vars}"
	chown "${user}:${user}" "${ovmf_vars}"
fi


echo "Tap Dev: ${tap_dev}"
echo "Mac Address: ${tap_mac}"
echo "Image: ${img}"
echo "OVMF Vars: ${ovmf_vars}"
echo "lower netdev: ${lower_netdev}"

# Drop privileges before starting QEMU
EUID=0 USERNAME="${user}"
qemu-system-x86_64 -machine q35 \
	-boot menu=on \
	-drive if=pflash,format=raw,readonly=on,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd \
	-drive if=pflash,format=raw,file="${ovmf_vars}" \
	-bios /usr/share/edk2/x64/OVMF_CODE.4m.fd \
	-boot menu=on \
	-enable-kvm -smp 4 -m 4G \
	-drive file="${img}",format="${img_format}",if=virtio \
	-netdev tap,fd="${tap_fd}",id=hostnet0,vhost=on -device virtio-net-pci,netdev=hostnet0,id=net0,mac=$tap_mac \
	-device virtio-serial \
	-display none \
	-monitor unix:"${img}."monitor.sock,server,nowait \
	-serial unix:"${img}."console.sock,server,nowait \

Note: In the future I'd love to use systemd-networkd's MacVTAP support. A basic version of that can be found in an earlier revision but this requires more configuration files. Sadly I haven't found a way to get the MacVTAP fd directly from the systemd unit file e.g. via OpenFile= because the exact /dev/tapXY to be opened is dynamic. In the above launch script this information is gathered from sysfs but I haven't found a way to do this directly in a unit file.

A matching systemd unit file to start a VM using the above script with the example /var/vms/chimera.img disk image is listed below. This uses the lower netdev's systemd-networkd device as After= and Wants= to ensure ordering. See later for the minimal configuration.

The MacVTAP is deleted on stopping the VM using ExecStopPost=.

Saved in /etc/systemd/system/chimera_vm.service

[Unit]
Description=Chimera Linux VM
After=sys-subsystem-net-devices-enp2s0.device
Wants=sys-subsystem-net-devices-enp2s0.device

[Service]
Type=simple
ExecStart=/usr/local/bin/launch_vm /var/vms/chimera.img d2:f3:32:c9:83:93 enp2s0
ExecStopPost=ip link del chimera_macvtap

[Install]
WantedBy=multi-user.target

In my network configuration the enp2s0 interface is a secondary port on my NAS and not used for host networking. This removes the need for the switch to support hairpin mode for the host to talk to the guests.

Saved in /etc/systemd/network/30-virttap-lower.network

[Match]
Name=enp2s0

[Network]
DHCP=no
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment