Tailscale on OpenBSD

I spent some time setting this up today evening and thought I’d post the steps here. Nothing fancy, just putting together various pieces actually.

I assume you know what Tailscale is; if not check out their website. Basically it is a mesh network built on top of Wireguard. Using it you can have all your devices both within your LAN(s) and outside be on one overlay network as if they are all on the same LAN and can talk to each other. It’s my new favourite thing!

Tailscale is open source and you can find the source on GitHub. Building it from source is easy too – just clone the repo (or download a release tarball and unzip) then build it so:

I tried that with the current -stable branch of OpenBSD, which is 6.7. Unfortunately that has Go 1.13 and compiling Tailscale requires Go 1.14 or above. So the first thing I did was switch to the -current branch. This is easy nowadays, all you have to do is sysupgrade -sand it will download a snapshot of -current, install, and reboot. Of course there is a risk to running -current but this is in my home lab so I am not too fussed.

With that out of the way go back to the downloaded source code and run the command above. This will install two binaries in $HOME/go/bin. Copy these over to /usr/sbin.

Next,create a startup script so tailscaled is automatically run by the rc scripts. Create a file called /etc/rc.d/tailscaled and put the following in it (I’d do doas nano -w /etc/rc.d/tailscaled and paste the below):

(Note: The above code has changed since I posted it initially).

Make the file you create non-writable & executable

Enable the service via rcctl:

And now you can start it via doas rcctl start tailscaled and also do a tailscale up to register your device as usual. If you reboot, tailscaled automatically connects to your Tailscale network.

More Info

Initially I had put in some more info above but decided to move it to a separate section here.

The rc script has two functions: rc_start() and rc_stop().

The rc_start() function will run tailscaled and send its logs to /var/log/messages (thanks to this article for the idea). I added rc_bg=YES to force starting the daemon in the background (alternatively I could have added an & after logger -t tailscaled).

Initially I had toyed with an rc_stop() function too which would run tailscaled with the --cleanup switch, but that never worked. Even when running it manually it would appear to cleanup but didn’t really do anything. Must be something related to OpenBSD, I guess… dunno.

The rc_stop() function calls tailscaled with the --cleanup switch to clean things up. It is supposed to stop the process too but doesn’t (some OpenBSD quirk I guess) so I kill it manually. I output this to the logs.

One of the errors thrown by --cleanup was about not finding the interface:

This is probably due to wireguard-go and OpenBSD (Tailscale uses wireguard-go). It looks like wireguard-go needs you to specify the tunnel interface manually in OpenBSD:

Since the tun driver cannot have arbitrary interface names, you must either use tun[0-9]+ for an explicit interface name or tun to have the program select one for you. If you choose tun as the interface name, and the environment variable WG_TUN_NAME_FILE is defined, then the actual name of the interface chosen by the kernel is written to the file specified by that variable.

I didn’t have any issues starting tailscaled without a tunnel specified, but considering it gave me an error when trying to stop I decided to specify the tunnel both when starting up and shutting down. Of course I’d also want to have the option to override this so I take advantage of the daemon_flags option of OpenBSD’s rc system for this. 

Typically you’d configure a daemon via /etc/rc.conf (or /etc/rc.conf.local in this case) using the daemon_flags variable (replace “daemon” with “tailscaled” in this case). If daemon_flags=NO then the daemon isn’t started while if daemon_flags= (empty) then it is started. You can also override any flags by specifying something to daemon_flags. With tailscaled I am passing the --port 0 --tun tun0 as flags by default to tell it to automatically select a port to listen to (--port 0) and use tun0 as the tunnel interface (--tun 0) but if someone wants to override that all they need to do is use a different set of parameters in their tailscaled_flags.

I also wanted to explicitly specify the state and socket files to tailscaled. This is not strictly needed as they default (on OpenBSD) to whatever I have specified in the rc script, but I thought I’d make it explicit anyways. Since I don’t expect users to override these I define these via a daemon_args variable, just for my internal use.

Updates

Update: Someone kindly logged a bug report for --cleanup not working.

Update2: I modified the blog post with a revised script and also added more explanations to the rc script as I tweaked it a bit.