Vmm /

Configure vmm on OpenBSD

OpenBSD can support virtual machines with vmd(8). The guide below is inspired by the virtualization guide from the OpenBSD FAQ.

vmd requires a processor that supports SLAT for AMD or EPT for Intel. Check support with this command:

$ dmesg | egrep '(VMX/EPT|SVM/RVI)'

Once you have confirmed your hardware can handle virtualization, install vmm-firmware:

$ doas fw_update

By default, there are only four tap interfaces. We need to create sufficient devices for all our virtual machines:

# cd /dev
# for i in $(jot 50 4 50); do sh MAKEDEV tap$i; done

We'll increase arpq because we may have many virtual machines on the same switch:

# sysctl net.inet.ip.arpq.maxlen=1024
# echo "net.inet.ip.arpq.maxlen=1024" >> /etc/sysctl.conf

We will need to permit IPv4 and IPv6 forwarding for our virtual machines:

# sysctl net.inet.ip.forwarding=1
# echo "net.inet.ip.forwarding=1" >> /etc/sysctl.conf
# sysctl net.inet6.ip6.forwarding=1
# echo "net.inet6.ip6.forwarding=1" >> /etc/sysctl.conf

Configuring networking devices

We need a bridge to connect together all the tap? interfaces from our virtual machines. Although we could use bridge?, veb may offer better performance. We will therefore combine veb and vport? together to handle routing of traffic from tap(4) interfaces.

# cat /etc/hostname.if0
up
# cat /etc/hostname.veb0
add if0
add vport0
link1
# cat /etc/hostname.vport0
inet 192.168.0.1 255.255.255.0
inet6 2001:db8:: 48
lladdr bb:cc:dd:ee:ff:11
up

All traffic from the physical interface if0 (replace with your actual interface) will be bridged to all interfaces in veb0, including vport0, which defines your dedicated server's IP address.

Replace 192.168.0.1 with the dedicated server's actual IP address. Replace 255.255.255.0 with the correct subnet mask. Similarly, replace 2001:db8:: with the dedicated server's IPv6 address, and replace 48 with the correct subnet mask.

It's recommended to define the logical link address (lladdr) for vport0, otherwise it may change upon reboot. If the physical address changes, this may confuse devices that rely on that address for routing, causing networking issues. By defining lladdr, we avoid that problem.

For veb0, we specify link1 in order to allow filtering on this interface (see veb(4)).

NOTE: You must add up in /etc/hostname.vport0. vport(4) interfaces do not come up automatically.

Configuring vm.conf

We create vm.conf(5):

# cat /etc/vm.conf
socket owner :vmdusers

switch "switch0" {
    group vms
    locked lladdr
    interface veb0
}

obsd="/home/iso/install76.iso"

vm "username" {
    owner username
    memory 2048M
    cdrom $obsd
    disk /home/username/username.qcow2 format qcow2
    interface tap0 { 
        locked lladdr aa:bb:cc:dd:ee:01
        switch "switch0"
    }
}

First, we set group vmdusers as the owner of the /var/run/vmd.sock. Only users in the vmdusers group will be able to start or stop virtual machine with vmctl.

Next, we define a virtual switch block for switch0. The interface will belong to group vms, which provides us an easy way to filter with pf?. locked lladdr prevents spoofing of hardware addresses. veb0 will be the network interface used by the switch.

We then define a macro for the OpenBSD ISO.

Next, we define a virtual machine named username owned by username with 2GB of memory, the CDROM with the OpenBSD ISO, and we lock the networking interface to tap0 and explicitly define a hardware address. We put this interface on switch0.

WARNING: Do not use aa:bb:cc:dd:ee:xx. This must be replaced with your own random lladdr address.

WARNING: Do not pick a broadcast MAC address. If the first octet of the address is an odd number (such as f1:xx:xx:xx:xx:xx or f3:xx:xx:xx:xx:xx), it will appear as a broadcast device and may be the cause of routing issues.

Create qcow2 images

Make sure to create qcow2 images for every disk specified in vm.conf(5). Run these commands with the respective user:

$ vmctl create -s 20G $PWD/$USER.qcow2

Preparing ISO files

Next, we download our OpenBSD ISO.

$ doas useradd -m -g =uid -c "iso" -d /home/iso -s /sbin/nologin iso
$ ftp https://cdn.openbsd.org/pub/OpenBSD/7.6/amd64/install76.iso
$ ftp https://cdn.openbsd.org/pub/OpenBSD/7.6/amd64/SHA256.sig
$ signify -C -p /etc/signify/openbsd-76-base.pub -x SHA256.sig install76.iso
Signature Verified
install76.iso: OK
$ doas mv install76.iso /home/iso/
$ doas mv SHA256.sig /home/iso/
$ doas chown -R iso:iso /home/iso/

If the signature does not verify, delete the ISO and don't proceed.

We will want to enable and start vmd:

$ doas rcctl enable vmd
$ doas rcctl start vmd  

We need to create a new group vmdusers for each of our users so they can access the serial console:

# groupadd vmdusers
# chown root:vmdusers /var/run/vmd.sock

For each virtual machine, we create a user and a disk image using install.pl:

$ ./install.pl $USERNAME

Configuring Packet Filter

Inside pf.conf(5), we add a rule to block all traffic from the vms group (all virtual machines), while allowing ICMP:

block drop on vms all
pass on vms proto {icmp icmp6}

After this line, whitelist traffic from each individual machine's IP addresses:

pass on tap1 from {192.168.0.2 2001:db8:1234:5678::/64} to any
pass on tap1 from any to {192.168.0.2 2001:db8:1234:5678::/64}

Replace 192.168.0.2 with the actual IPv4 address, and 2001:db8:1234:5678::/64 with the actual IPv6 subnet. You will need two lines for each virtual machine

Reload the ruleset with pfctl?:

# pfctl -f /etc/pf.conf

Troubleshooting