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:
1 |
go install tailscale.com/cmd/tailscale{,d} |
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 -s
and 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/local/sbin
. (An earlier version of this post suggested copying them to /usr/sbin
. Since then a reader Raf Czlonka wrote in to say /usr/sbin
is reserved for the OS and /usr/local/{,s}bin
is what must be used instead).
1 |
doas cp ~/go/bin/tailscale* /usr/local/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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#!/bin/ksh # # $OpenBSD: tailscaled,v 0.1 $ daemon="/usr/local/sbin/tailscaled --state=/var/db/tailscale/tailscaled.state --socket=/var/run/tailscale/tailscaled.sock" daemon_flags="--port 0 --tun tun0" rc_bg="YES" rc_reload="NO" . /etc/rc.d/rc.subr # these have to be placed below the above rc.subr sourcing so that they override # default value is: pexp="${daemon}${daemon_flags:+ ${daemon_flags}}" pexp="tailscaled" rc_start() { # default value is: ${rcexec} "${daemon} ${daemon_flags} ${_bg}" # ${_bg} gets replaced with '&' if rc_bg=YES ${rcexec} "${daemon} ${daemon_flags} 2>&1 | logger -t tailscaled ${_bg}" } rc_stop() { # default value is: pkill -xf "${pexp}" ${rcexec} "${daemon} --cleanup ${daemon_args} 2>&1 | logger -t tailscaled" pkill -xf "${pexp}" } rc_check() { # default value is: pgrep -q -xf "${pexp}" pgrep -q -x ${pexp} } rc_cmd $1 |
(Note: The above code has changed since I posted it initially).
Make the file you create non-writable & executable
1 |
doas chmod 555 /etc/rc.d/tailscaled |
Enable the service via rcctl:
1 |
doas rcctl enable tailscaled |
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 three functions: rc_start()
, rc_stop()
and rc_check()
.
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 coz I noticed the system startup would pause a few seconds when starting up tailscaled
. I could have added an &
after logger -t tailscaled
manually, but using rc_bg=YES
is the recommended way.
The default rc_start()
function is the following: "${daemon} ${daemon_flags} ${_bg}"
So what I am doing is modify that to also add some logging.
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.
The default rc_start()
function is the following: pkill -xf "${pexp}"
. It simply kills the process. That’s what I too eventually do but I try and do the cleanup anyways. The default ${pexp}
variable has the daemon command line and all the flags; I set ${pexp}
to be just the tailscaled
name via pexp="tailscaled"
.
One of the errors thrown by --cleanup
was about not finding the interface:
1 2 3 4 5 6 7 8 9 10 11 |
$ doas tailscaled --cleanup logtail started Program starting: vdate.20200921, Go 1.15.2: []string{"tailscaled", "--cleanup"} LogID: 8c25ac1345239c280d27f69e7aab07202e2d42579f2446a89fbc0c979b4923aa logpolicy: using system state directory "/var/db/tailscale" 1.0M/13.5M dns: using dns.directManager 1.1M/13.6M ifconfig down: exit status 1 ifconfig: SIOCGIFFLAGS: Device not configured flushing log. logger closing down logtail: dialed "log.tailscale.io:443" in 196ms |
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 ortun
to have the program select one for you. If you choosetun
as the interface name, and the environment variableWG_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.local
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. That’s why I add these to the daemon
variable.
Lastly, I have an rc_check()
function so rcctl check tailscaled works without always showing failed. By default rc_check()
does an pgrep -xf ${pexp}
and that wasn’t working for me because -f
checks for the process arguments too. So I override it to simply do pgrep -x ${pexp}
– i.e. not bother with the arguments.
Updates
Update: Someone kindly logged a bug report for --cleanup
not working.
Further Update: I modified the blog post with a revised script and also added more explanations to the rc
script as I tweaked it a bit. The script has been revised a few times with the last change made on 29th Oct 2020.
Update (12th Dec 2022): Now and then I keep getting questions on TailScale and OpenBSD. Today I realized that’s coz this blog post seems to be the top result when searching for these words. Tailscale now has support for OpenBSD and FreeBSD. It doesn’t appear in their Download page, but if you look at the script they provide under the Linux section it caters to these two BSDs. That’s probably a better bet than this blog post. :)