22nd and 29th April 2021

WordPress Security Workshop

Office Hours

Thursdays 10am-1PM GMT

Book a free 20 minute chat with Tim!

VPNs and fun experiments with UPnP


Here lies a tale of a fun adventure with helping a friend remotely set up a VPN server, the joys of UPnP and SystemD. It’s a sort of tutorial but more documenting my own notes for if I have to do it again in future.

I want a VPN server Tim

It all started when I was asked “I want a VPN server because I want to be nice and safe when travelling about” said my friend via Skype that bastion of privacy messaging service. Oh sure I said I can whip you up a VPN server and send it to you.

So the goal of the project, a small VPN server sitting inside his home network, which he could then connect to with a remote client (his laptop).

Building a VPN server

This was fairly painless a spare RasberryPi that was lying around the house a copy of Rasbian a Debian based distro to get an OS on there. Turning off the desktop x.org and associated components and setting up SSH access with a key on a random port (this was less about security and more not wishing to avoid conflict should my friend have something running a SSH server). Building the server, I kept in mind I might have little to no access and my friend while having physical access would at best be able to type careful commands via a console if lucky.

For the VPN server it self I used PiVPN which is a configuration tool for OpenVPN server installation is painless though has one of my biggest pet peeves. The install instructions are:

curl -L https://install.pivpn.io | bash

So let’s be clear a security product wants me to install a shell script from a remote source and then execute it without even a cursory look! something-something, grumble, grumble. However, the source is available from Github and once downloaded and piped through Bash like a dutiful sheep, using PiVPN is super easy and configuring the VPN is minimal.

Testing the VPN

Setting up the VPN probably took all of twenty minutes, and testing I went to my router control panel and went to open a port. That’s when the little voice started going… You’re going to have to talk him through port forwarding Tim. Now my friend is technical(ish) taking the Pi and plugging it in would not be an issue. Installing a OpenVPN client and following instructions again not an issue. The problem was I had no idea what router he had and from the discussion that followed it appeared to be a generic box. One he wasn’t even sure of the password. Given he used Steam and Skype I made a wild assumption that it supported UPnP and so decided to simply open the ports that would be needed via UPnP.


Universal Plug and Play is a set of protocols that lets applications talk to switches and routers to enable port forwarding its actually way more then that, its a communication layer for devices to talk to each other. For what I want it should allow me to request that router open and allow traffic to and from the Pi on several ports.

The ease you can basically break out of a network using UPnP is frightening and I think I should say that my advice is:

Good friends don’t let their friends have UPnP enabled

However I’m not a good friend

UPnP clients

I initially looked at writing a small script to open UPnP most programming languages have classes for working with UPnP. However to test everything was working I used miniupnp which is a small client and server for talking over UPnP. In the end I just used the client for this project.

Indeed opening a port is as simple as:

upnpc -a $IP 11948 11948 UDP

where $IP is the machine IP that you wish to pass the port to, you can also specify if you want TCP, UDP or both.

There is something very weird about just downloading a networking tool client, running a single command and it just work. I’m use to spending hours dealing with bridges and network interfaces on multiple devices before anything works!

Realising I could just use upnpc to manage the port opening, it was a case of creating an incredibly simple bash script and a couple of SystemD controllers specifically a SystemD service unit and a Timer (to keep periodically checking and repunching the hole/Letting the remote DNS.

Everyone loves SystemD

Setting up SystemD units and timers is fairly trivial first you need to set up a service file, with some basic description, usefully you can specify what your unit both depends on and the minimum point it should be called. For example, in my case, there is no point trying to use it till after networking is up and running.

Description=Hole Punching for OpenVPN

ExecStart=/usr/local/bin/holepunch.sh >> /var/log/holepunch.log 2>&1

You can then specify its “type”, in this case, a one-off activity because we are going to then use the timer to call it, rather than a service. Finally you tell it what to fire in my case a shell script with it’s output very verbosely going to a log file.

Next up is the timer unit which is a replacement for the cron aspect, in this case the timer is set to wait 5 minutes then run and then rerun every 30 minutes.

Description=Runs and manages Holepunch services

# Time to wait after booting before we run first time
#Run every 30min

Why wait 5 minutes? Yeah I’m lazy and well when I said the networking just worked, working out at what stage it would work took more then 5 seconds, while in theory network-online should have been the correct point and should have been instant, the reality was this didn’t quite behave as expected. the 5 minutes means post reboot the holepunch should have kicked in.

echo $(date -u) Starting Up
IP=$(hostname -I | grep -o '^\S*')
upnpc -a $IP 11948 11948 UDP
upnpc -a $IP 11948 11948 TCP
echo "======= END ======="

The script has been heavily simplified, it does a bunch of other stuff, like sending a CURL request to a VPS to update the DNS on the fly. Originally I was going to put in some basic checks so that upnpc didn’t constantly barrage the router, but in this side project that somehow got missed, going forward if I need to do this again, I will set up some remote testing to make sure the ports are accessible and if not then punch though. By default OpenVPN is set to do UDP and fallback to TCP, originally during testing I was just using UDP but added TCP later after some feedback.

systemctl enable holepunch
systemctl start holepunch

Finally we just enable our service, which also enabled our timer and everything is sorted. One VPN server set up, managing itself.

Loads of improvements I can make, and in the end the core reason for using SystemD timers (to make sure we controlled that everything was ready for our script) actually turned out to not fully work, so a cron might have been easier. I console myself with the belief that in years to come sysadmin Tim will thank me for making it a service so it will be obvious what’s happening. I’m unconvinced, so am writing this blog post to basically remind older me of this.