WhyBhyve is the FreeBSD native virtualization system, it allows to run a full-operating system in an isolated process.

See also
Build information

Ensure the following options:

sysutils/vm-bhyve
[x] BHYVE_FIRMWARE  Required to run UEFI guests
[x] EXAMPLES        Install example guest templates
[x] GRUB2_BHYVE     Required to run any guests that need a Grub bootloader
[x] TMUX            Tmux console access instead of cu/nmdm

Configuration

vm-bhyve

Fist we need to create a directory where vm-bhyve will store it’s configuration as well as the various VMs created:

Creating vm-bhyve directory
zfs create -o mountpoint=vm-bhyve directory vm-bhyve dataset  # Create vm-bhyve directory
zfs set compress=zstd vm-bhyve dataset                # Enable compression if desired

Enabling vm-bhyve and specifying it’s directory is done in the system configuration file rc.conf:

vm_enable="YES"
vm_dir="zfs:vm-bhyve dataset"

Allows vm-bhyve to initialize it’s infrastructure:

Initialize vm-bhyve infrastructure
vm init

Various templates are available in /usr/local/share/examples/vm-bhyve/, they should be copied (and adapted) in the .templates directory inside the defined in vm_dir (in case of a zfs filesystem, the mountpoint is used).

Populate with predefined templates
cp /usr/local/share/examples/vm-bhyve/* vm-bhyve directory/.templates/

Networking

The bridge interface on which bhyve will attach is manually configured, instead of letting vm-bhyve manage it, so it’s easier to integrate on other part of the system.

Element Description
wan0 Interface to the outside world
bridge1 Cloned bridge interface
vmnet0 Renamed bridge1 interface
bhyve-bridge Name given to the ‘switch’ is vm-bhyve
192.168.100.0/24 Subnet dedicated to VMs
192.168.100.1 IP address of the host on the VM subnet
192.168.1.1 IP address of the host

Create the vmnet0 interface and assign it an IP address, the interface is also given a group name jail to ease management in complex firewall rules:

cloned_interfaces="bridge1"
ifconfig_bridge1_name="Dedicated network interface"
ifconfig_Dedicated network interface="inet 192.168.100.1/24 group jail"

If we want to give the VM access to the outside world we need to have the host act as a gateway, and also as here the VMs have been defined on a private network NAT must be enabled:

nat pass on External interface inet from Dedicated network interface:network to any -> External interface
gateway_enable="yes"
pf_enable="yes"

It is possible to use, for a more complex configuration, a full featured DHCP, TFTP and DNS server to provide the necessary network services for booting and network access, but we have chosen here to use dnsmasq which conveniently provide all these services with a simple configuration:

# Select listening interface
interface=Dedicated network interface        # Select the interface on which to listen
except-interface=lo0    # Ensure we don't listen on loopback 
bind-interfaces         # Bind to interfaces instead of listening on wildcard

# DNS (with DNSSEC enabled)
domain-needed
conf-file=/usr/local/share/dnsmasq/trust-anchors.conf
dnssec
dnssec-check-unsigned

# DHCP
dhcp-authoritative
dhcp-range=Range of dynamically allocated addresses,Lease time
# Using statically allocated addresses for some guests
#  infinite = maximum lease time
dhcp-host=Hostname,Assigned address                      # from provided hostname
dhcp-host=MAC address,Assigned address,Lease time  # from MAC address

We now let vm-bhyve know that we have created an interface where it will be able to connect the VMs:

# List of available switch
switch_list="bhyve-bridge"

# Switch definition
type_bhyve-bridge="manual"
bridge_bhyve-bridge="Dedicated network interface"

named doesn’t support tapX interface created by vm-bhyve and get stuck on it so we are forced to specified listen address (directive: listen-on and listen-on-v6) of all the desired intefaces so to skip tapX (no more keyword: any).

For example:

options {
        listen-on       { List of IP addresses on which to bind };
        listen-on-v6    { none; };
}

VM

Templates

As we are using zfs, the various templates will be adapted to use a sparse zvol as disk, and it will be named root

disk0_name="root"
disk0_dev="sparse-zvol"

Console

There is a virtio console, which provide a socket where to connect (with socat for example), but:

For a true console, we need to use vm console which connect (defined when configuring the VM) to an emulated serial line:

Shared directory

bhyve_options="-s PCI slot,virtio-9p,Share name=Shared directory,Share name=Shared directory

Sound

bhyve_options="-s PCI slot,hda,play=/dev/dsp,rec=/dev/dsp"

Entropy driver

To supply high-quality randomness from the hypervisor to the guest:

virt_random="yes"

The guest will also need a specific driver:

Guests

Windows

Executing command at startup:

Windows registry
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run]
"F Drive"="subst F: D:\\mount\\db"
"G Drive"="subst G: D:\\mount\\log"

Linux

Console

The virtio console is available through named devices in /dev/virtio-ports/ directory (which hold seem links to numbered device /dev/vport?p?).

Give user necessary right to access virtio console
usermod -a -G dialout User login
usermod -a -G tty User login

Shared directory

Mounting shared directory
# See: https://www.kernel.org/doc/html/latest/filesystems/9p.html
mount -t 9p -o trans=virtio,cache=mmap,msize=512000 sharename /path/to/mountpoint

Commands

Create a Debian VM from template
vm create -t debian debian  # Create VM from template
vm configure debian         # Perform extra configuration
Fetch iso image
debian_cd_repos_url=https://cdimage.debian.org/debian-cd/current/amd64/iso-cd
vm iso ${debian_cd_repos_url}/debian-10.9.0-amd64-netinst.iso
Install from iso image
vm install debian debian-10.9.0-amd64-netinst.iso
Start VM
vm start debian
Access VM console
vm console debian
Terminate VM
vm stop debian        # Ask for gracefull shutdown
vm poweroff debian    # Terminate immeditately

Troubleshouting

All ports busy (vm console)

# vm console debian
all ports busy

There is a cu process already connected to the console, find it and kill it