X11 Graphics and sndio(7) Sound With vmm(4) Guest Virtual Machines

It is assumed that my readers have basic familiarity with operating guest virtual machines under OpenBSD’s vmm(4) hypervisor.

 

The OpenBSD FAQ’s virtualization chapter tells us that graphics are not available to guest virtual machines.  That statement is true, in that there are no virtual graphics cards for guest virtual machines, and all guests require serial consoles.  That said, you can still run a variety of graphical applications on these guests, as long as the graphics are over a network connection rather than through a virtual graphics card.  Such as:

I have a use case for a native X application in a guest virtual machine on my hosting laptop.  It needs to present X11 graphics in an X window, integrated with my host laptop’s window manager, and it needs to produce sound on the host laptop’s speakers.

My application does not require video or other high-performance graphics that might be a poor fit for the latency and bandwidth limitations inherent in X11 networking.  If it did, I would consider a VNC-based solution, giving up my preference for integration with the host window manager.

 

Specifications

Guest Networking

This guest virtual machine is provisioned with a local interface, to have the same auto-configuration service as any other guest I have provisioned.  As described in the vm.conf(5) man page and in the Virtualization chapter of the FAQ,  a local interface automatically generates an IPv4 subnet for the guest, with a configured gateway tap(4) connection on the host, and the guests are provided with DHCP auto-configuration.

Local interfaces also assign the host gateway as a DNS nameserver, which I redirect to a caching resolver.

I provision IPv4 packet forwarding and Network Address Translation for the shared address space used by guest VMs, so that all guests are able to communicate beyond the host laptop.

Guest Graphics

X graphics are tunneled via SSH, which mitigates many of the security risks of using networked X11 graphics.  SSH also integrates the guest’s X application nicely with the host’s window manager.  I also use trusted X11 forwarding so that I am able to seamlessly copy/paste text between the guest and host X applications.

Guest Sound

OpenBSD’s sndio(7) library permits the sending of audio streams over a network.  The host laptop’s sndiod(8) server need only have a network listener provisioned, and the guest directed to the listener via provisioning the network-connected sndio(7) audio device.  Also, as the host’s gateway tap(4) device is only present and configured when the guest is running, I use a veb(4)/vport(4) bridge to provide the permanent address needed by sndiod(8) to open and retain its TCP socket.

 

Host Provisioning

 

NAT and DNS

Local interfaces use the 100.64/10 shared address space by default.  These two lines in the host laptop’s pf.conf(5) rule set provision Network Address Translation (NAT) for all guests’ communications with the outside world, and redirect all their domain queries.  I happen to use my host laptop’s unbound(8) caching resolver for domain name resolution, listening on the `localhost` loopback interface, but guest domain queries could be redirected to an external nameserver, caching or authoritative.

pass out log from 100.64.0.0/10 to any nat-to (egress)
pass in log proto { tcp udp } from 100.64.0.0/10 to any port domain rdr-to localhost

IPv4 Forwarding

With this enabled in sysctl.conf(5), the host acts as a NAT router and guest virtual machines are able to communicate beyond the host.

net.inet.ip.forwarding=1

VM Provisioning

The guest has been provisioned in the laptop’s vm.conf(5) with a statically assigned tap(4) device tap3, and with a permanent switch, the veb(4) bridge veb0.  I used the label “perm” only to remind myself why I have it provisioned.  

vm xguest {
     disable
     owner jggimi
     
memory 2g
     
disk /home/jggimi/vm/xguest.qcow2
     local interface tap3 switch perm
}
switch perm {
     interface veb0
}

This guest is `disabled` so that it does not start automatically during boot up.  The tap(4) device tap3 will not be created until the guest is started by a vmctl(8) `start` command.

Using a Switch for sndiod(8)

Without a switch, tap3 would be created and assigned the 100.64.3.2 address only when the virtual machine is started, and the device and address would be deleted when the virtual machine is stopped. The sndiod(8) audio server needs to open a TCP socket at this address upon boot, and keep it open, whether or not the guest is operational.

I have provisioned a `switch` in vm.conf(5) and provisioned veb(4)/vport(4) devices as shown below, so that sndiod(8) always has a listening TCP socket available, whether or not the tap(4) device is present.  The vport(4) holds the address permanently:

/etc/hostname.veb0:

add vport0
up

/etc/hostname.vport0:

inet 100.64.3.2/31
up

The sndiod(8) audio server has been provisioned to listen to this permanent address:

# rcctl set sndiod flags -L 100.64.3.2
# rcctl restart sndiod

Guest VM Name Resolution

My laptop host’s resolv.conf(5) includes `lookup file bind` so that I can make quick domain name resolutions via a hosts(5) file, as my caching resolver is provisioned with LAN devices rather than ephemeral guests.  I have added:

100.64.3.3      xguest

Provisioning the Host ssh(1) Client

My ~/.ssh/config includes provisioning to request trusted X11 forwarding, and a control socket for multiplexing.  This control socket makes the termination of the tunnel controllable by ssh(1) command.  See the ssh_config(5) man page for these provisions:

Host xguest
     ForwardX11 yes
     ForwardX11Trusted yes
     ControlPath ~/.ssh/sockets/%r@%h:%p
     ControlMaster auto

Convenience Shell Script

I use a simple shell script to start/stop the guest and the X application.  Take note of the -f and the -O options, and if you are unfamiliar with them, please see the ssh(1) man page for details.

#!/bin/sh
usage() {
     echo "argument: 'start' or 'stop' the vm, 'app', or 'end' the X application."
     echo "argument: 'test' to test audio, 'ssh' to connect to a shell."
     exit 1
}
if [ $# -eq 0 ]; then
     usage
fi
case $1 in
     start) vmctl start xguest ;;

     stop) ssh xguest shutdown -h now ;;
     app) ssh -f xguest /home/jggimi/bin/xguest-app ;;
     end) ssh -O exit xguest ;;
     test) ssh xguest /home/jggimi/bin/testaudio ;;
     ssh) ssh xguest ;;
     *) usage ;;
esac

Guest Provisioning

X11 Forwarding

The guest’s sshd(8) server must permit X11 forwarding, as defined in sshd_config(5):

X11Forwarding yes

SSH Authorized Keys

While not a strict requirement, I do not use passwords with SSH, instead I deploy public key authentication with all my SSH clients and servers.  To do this, I place the contents of my SSH client user’s public key (e.g.: ~/.ssh/id_ed25519.pub on the client) into the Authorized Keys file of the SSH server user (e.g.: ~/.ssh/authorized_keys on the server).   The private key is never shared.

Provisioning the sndio(7) $AUDIODEVICE

I find it easiest to provision the AUDIODEVICE environment variable in a local script on the guest that then runs the X application.  Above, I’d provisioned my convenience script to run ssh(1) -f, starting the local script xguest-app.  That script contains three lines, and transmits the audio from the application to my host’s sndiod(8) server, which is listening to a TCP socket at the 100.64.3.2 address bridged via vport0 and veb0.

#!/bin/sh
export AUDIODEVICE=snd@100.64.3.2/0
/usr/local/bin/<my X application>

The local testaudio script sends a short burst of static from the guest to the host, using the contents of the /bin/test executable as if it was an audio file:

#!/bin/sh
export AUDIODEVICE=snd@100.64.3.2/0
aucat -i /bin/test