Running Virtual Machines with VMM on OpenBSD

OpenBSD has this small, nice virtual machine monitor (VMM) you can use to run accelerated virtual machines (VM) on OpenBSD. This article describes how to set up vmm(4) and how to run various operating systems.

Initial Setup

I run -current on my personal machine and thus all of the following applies to the version as of September 2018.

Quick Boot of a CD-ROM ISO Image

Sometimes, it’s not desired to create a disk image plus a VM configuration, e.g., if you only want to fire up an ISO image to see if it boots. Use vmctl start and the -r option to specify the path to the ISO. Additionally, set -c to see the serial console output. vmctl will ask you for a name so set a random one since the name doesn’t matter here:

# vmctl start foo -r cd64.iso -c
vmctl: starting without disks
vmctl: starting without network interfaces
Connected to /dev/ttypc (speed 115200)
SeaBIOS (version 1.11.0p0-OpenBSD-vmm)
CD-ROM: E0
Loading /6.4/AMD64/CDBOOT
probing: pc0 com0 mem[638K 510M a20=on]
disk: cd0
>> OpenBSD/amd64 CDBOOT 3.40
boot>
cannot open cd0a:/etc/random.seed: No such file or directory
booting cd0a:/6.4/amd64/bsd.rd: 3511114+1500160+3887944+0+593920 [372415+111+440712+293098]=0xa1e528
entry point at 0x1000158
Copyright (c) 1982, 1986, 1989, 1991, 1993
        The Regents of the University of California.  All rights reserved.
Copyright (c) 1995-2018 OpenBSD. All rights reserved.  https://www.OpenBSD.org

OpenBSD 6.4-beta (RAMDISK_CD) #305: Sun Sep 23 23:34:36 MDT 2018
    deraadt@amd64.openbsd.org:/usr/src/sys/arch/amd64/compile/RAMDISK_CD
[...]

Create a Hard Disk

In most cases, you want to have a persistent storage for your VM. Use vmctl or qemu-img to create a new hard disk. It doesn’t matter if you create the image with vmctl or qemu-img, however, the first one is include in base. Use -f to switch between raw and the more modern qcow2 format. The following command creates a qcow2 format disk images with a maximum size of 10 GB:

$ vmctl create vm-disk.img -s 10G -f qcow2
vmctl: imagefile created
$ ll -ls vm-disk.img
  1 -rw-------  1 xhr  wheel  -  256K Sep 24 12:34 vm-disk.img

Connect your VMs to the Network

There are several ways to connect guest VMs to the network. The simplest one and the one I use is called local interfaces. A local interface is added to a VM by using the -L option with vmctl start. You will get a vio(4) interface within the VM connected to a tap(4) interface on the host. All IP addresses come from the shared, globally non-routed 100.64.0.0/10 address pool and served from a buit-in DHCP server. For the logic on how the IP addresses in the VM and the host are choose read the excellent man page of vmctl.

In the following example I use the same command as above but this time with the -L option. Note how the vio0 interface gets an IP address via the built-in DHCP server:

$ doas vmctl start foo -r cd64.iso -c -L
[...]
Welcome to the OpenBSD/amd64 6.4 installation program.
(I)nstall, (U)pgrade, (A)utoinstall or (S)hell? s
# dhclient vio0
vio0: bound to 100.64.8.3 from 100.64.8.2 (fe:e1:bb:d1:5e:b4)
# ifconfig vio0
vio0: flags=8b43<UP,BROADCAST,RUNNING,PROMISC,ALLMULTI,SIMPLEX,MULTICAST> mtu 1500
        lladdr fe:e1:bb:d1:5e:b3
        llprio 3
        groups: egress
        media: Ethernet autoselect
        status: active
        inet 100.64.8.3 netmask 0xfffffffe

On the host, a tap(4) interface is now visible:

tap0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
        lladdr fe:e1:ba:d0:85:ce
        description: vm8-if0-foo
        index 7 priority 0 llprio 3
        groups: tap
        status: active
        inet 100.64.8.2 netmask 0xfffffffe

This is sufficient if you just want to have a connection between the VM and the host, If you want to grant Internet access to the VM via NAT you have to set up some pf(4) rules on the host. Add the following lines to your pf.conf and set the variables accordingly. The second rule is only needed if you want to redirect the VM’s DNS traffic to $dns_server. Usually, the DHCP sets the IP address of the host as DNS server.

match out on $ext_if from 100.64.0.0/10 to any nat-to $ext_if
pass out on $ext_if from 100.64.0.0/10 to any nat-to $ext_if

pass in proto udp from 100.64.0.0/10 to any port domain \
        rdr-to $dns_server port domain

On the host you also have to enable IP forwarding via sysctl. To persistently enable forwarding set the value in /etc/sysctl.conf. While the former is active immediately, the latters needs a reboot.

# sysctl net.inet.ip.forwarding=1
net.inet.ip.forwarding: 0 -> 1
# grep forwarding /etc/sysctl.conf
net.inet.ip.forwarding=1        # 1=Permit forwarding (routing) of IPv4 packets

Install and run an OpenBSD VM

Installing OpenBSD on vmm is really simple. Create a hard disk as described above and use the last vmctl command from above and add the -d option with the path to the disk image to the start command. If you use a qcow2 disk image, you have to prefix the path with qcow2:. For raw disks no prefix is needed. By default, a VM gets 512M RAM assigned, if you need more, just use -m size.

# vmctl start foo -r cd64.iso -c -L -m 1024M -d qcow2:vm-disk.img
Welcome to the OpenBSD/amd64 6.4 installation program.
(I)nstall, (U)pgrade, (A)utoinstall or (S)hell? i
At any prompt except password prompts you can escape to a shell by
typing '!'. Default answers are shown in []'s and are selected by
pressing RETURN.  You can exit this program at any time by pressing
Control-C, but this can leave your system in an inconsistent state.

Terminal type? [vt220]
System hostname? (short form, e.g. 'foo') test

Available network interfaces are: vio0 vlan0.
Which network interface do you wish to configure? (or 'done') [vio0]
IPv4 address for vio0? (or 'dhcp' or 'none') [dhcp]
vio0: bound to 100.64.11.3 from 100.64.11.2 (fe:e1:bb:d1:d0:83)
IPv6 address for vio0? (or 'autoconf' or 'none') [none]
Available network interfaces are: vio0 vlan0.
Which network interface do you wish to configure? (or 'done') [done]
DNS domain name? (e.g. 'example.com') [my.domain]
Using DNS nameservers at 100.64.11.2

Password for root account? (will not echo)
Password for root account? (again)
Start sshd(8) by default? [yes]
Change the default console to com0? [yes]
Available speeds are: 9600 19200 38400 57600 115200.
Which speed should com0 use? (or 'done') [115200]
Setup a user? (enter a lower-case loginname, or 'no') [no]
Since no user was setup, root logins via sshd(8) might be useful.
WARNING: root is targeted by password guessing attacks, pubkeys are safer.
Allow root ssh login? (yes, no, prohibit-password) [no] yes

Which disk is the root disk? ('?' for details) [sd0]
No valid MBR or GPT.
Use (W)hole disk MBR, whole disk (G)PT or (E)dit? [whole]
Setting OpenBSD MBR partition to whole sd0...done.
The auto-allocated layout for sd0 is:
#                size           offset  fstype [fsize bsize   cpg]
  a:           152.7M               64  4.2BSD   2048 16384     1 # /
  b:            85.5M           312864    swap
  c:         10240.0M                0  unused
  d:           124.4M           487904  4.2BSD   2048 16384     1 # /tmp
  e:            87.1M           742624  4.2BSD   2048 16384     1 # /var
  f:           902.7M           921024  4.2BSD   2048 16384     1 # /usr
  g:           385.6M          2769824  4.2BSD   2048 16384     1 # /usr/X11R6
  h:          1032.2M          3559616  4.2BSD   2048 16384     1 # /usr/local
  i:          1301.1M          5673568  4.2BSD   2048 16384     1 # /usr/src
  j:          5122.2M          8338208  4.2BSD   2048 16384     1 # /usr/obj
  k:          1043.1M         18828448  4.2BSD   2048 16384     1 # /home
Use (A)uto layout, (E)dit auto layout, or create (C)ustom layout? [a]
newfs: reduced number of fragments per cylinder group from 19544 to 19464 to enlarge last cylinder group
/dev/rsd0a: 152.7MB in 312800 sectors of 512 bytes
5 cylinder groups of 38.02MB, 2433 blocks, 4992 inodes each
/dev/rsd0k: 1043.1MB in 2136352 sectors of 512 bytes
6 cylinder groups of 202.47MB, 12958 blocks, 25984 inodes each
/dev/rsd0d: 124.4MB in 254720 sectors of 512 bytes
4 cylinder groups of 31.09MB, 1990 blocks, 4096 inodes each
/dev/rsd0f: 902.7MB in 1848800 sectors of 512 bytes
5 cylinder groups of 202.47MB, 12958 blocks, 25984 inodes each
newfs: reduced number of fragments per cylinder group from 49360 to 49160 to enlarge last cylinder group
/dev/rsd0g: 385.6MB in 789792 sectors of 512 bytes
5 cylinder groups of 96.02MB, 6145 blocks, 12416 inodes each
/dev/rsd0h: 1032.2MB in 2113952 sectors of 512 bytes
6 cylinder groups of 202.47MB, 12958 blocks, 25984 inodes each
/dev/rsd0j: 5122.2MB in 10490240 sectors of 512 bytes
26 cylinder groups of 202.47MB, 12958 blocks, 25984 inodes each
/dev/rsd0i: 1301.1MB in 2664640 sectors of 512 bytes
7 cylinder groups of 202.47MB, 12958 blocks, 25984 inodes each
newfs: reduced number of fragments per cylinder group from 11144 to 11096 to enlarge last cylinder group
/dev/rsd0e: 87.1MB in 178400 sectors of 512 bytes
5 cylinder groups of 21.67MB, 1387 blocks, 2816 inodes each
/dev/sd0a (817470d5e8e2306d.a) on /mnt type ffs (rw, asynchronous, local)
/dev/sd0k (817470d5e8e2306d.k) on /mnt/home type ffs (rw, asynchronous, local, nodev, nosuid)
/dev/sd0d (817470d5e8e2306d.d) on /mnt/tmp type ffs (rw, asynchronous, local, nodev, nosuid)
/dev/sd0f (817470d5e8e2306d.f) on /mnt/usr type ffs (rw, asynchronous, local, nodev)
/dev/sd0g (817470d5e8e2306d.g) on /mnt/usr/X11R6 type ffs (rw, asynchronous, local, nodev)
/dev/sd0h (817470d5e8e2306d.h) on /mnt/usr/local type ffs (rw, asynchronous, local, nodev)
/dev/sd0j (817470d5e8e2306d.j) on /mnt/usr/obj type ffs (rw, asynchronous, local, nodev, nosuid)
/dev/sd0i (817470d5e8e2306d.i) on /mnt/usr/src type ffs (rw, asynchronous, local, nodev, nosuid)
/dev/sd0e (817470d5e8e2306d.e) on /mnt/var type ffs (rw, asynchronous, local, nodev, nosuid)

Let's install the sets!
Location of sets? (cd0 disk http or 'done') [cd0]
[...]


Install and run a Linux VM

Install VMs via qemu

As of today, vmm(4) does not support enhanced graphics like VGA output so you need a guest operating system that supports a serial console. Since the serial console is not always active by default you can either try to activate it by modifying the ISO/boot image or you just install the guest operating system in qemu, activate the serial console and attach the disk to vmm(4) later. Be advised that vmm(4) currently does not support all Unix-like operating systems you might like. If in doubt, just give it a try or search the mailing list archive.

It doesn’t matter on which system you choose to install via qemu, so I prefer to do this on Linux since qemu on OpenBSD is not accelerated. An non-accelerated Debian Linux installation could easily take an hour. Since qemu and vmm(4) both supports the qcow2 disk format it is my preferred choice.

If you want to go with OpenBSD, install qemu via pkg_add:

# pkg_add qemu

Create a qcow2 disk image file as mentioned above and use the following command to boot the specified ISO image. This will give a graphical VGA console and you can easily install the operating system of your choice. In the following example I use Debian Linux.

# qemu-system-x86_64 -m 512 -no-fd-bootchk -net nic,model=e1000,macaddr=0a:54:00:4e:62:8a -net user \
    -cdrom /tmp/debian-9.5.0-amd64-netinst.iso -boot d debian.img

$Id: vmm.md,v 1.6 2020/05/16 07:44:02 cvs Exp $