In this blogpost series I’m going to go over how I created a site to site Virtual Private Network (abbreviated as VPN) for all of my personal devices. The best way to think about what this is doing is creating a logical (or imaginary) network on top of the network infrastructure that really exists. This allows me to expose private services so that only people I trust can even know how to connect to them. For extra convenience and battery saving power, I’m going to use WireGuard as the VPN protocol.
This series is going to be broken up into multiple posts about as follows:
By the end of this series you should be able to:
One of the most annoying parts of this exercise is going to be naming and numbering things, so let’s get that out of the way as soon as possible.
It’s a good idea to create a custom top level domain that won’t resolve on machines not inside your private network. This helps to prevent accidental information leakage by making it impossible for unauthorized third parties to resolve the name into a usable IP. If you don’t want to do this for any particular reason, it is possible to set things up as subdomains of an existing domain. This may also be preferable depending on your philosophical beliefs about what is a “valid” or “real” domain name, which is beyond the scope of this article.
Names are known to be hard with computer science things. The annoying part about naming things are what I call name collisions, or when someone else uses a name you were using. This most famously happened with .dev, making many tutorials referencing this old trick effectively useless. As such, it is better to choose names that are very, very unlikely to ever be added as a valid global top level domain. Try picking names by these criteria:
As such, this example will be using pele as the custom top level domain and name for this network.
Numbering your site to site private networks is another common pain point, mainly because conflicts in these spaces can be hairy to resolve. It can help to make a list of the IP space of all of the common networks you visit so you can make sure your network range doesn’t conflict with them:
# Network Range Details Home: 10.13.37.0/24 Work: 10.0.0.0/13
Generally people will pick routes out of the lower /12 of
10.0.0.0/8. This example will use the network range
10.55.0.0/16. Because WireGuard requires us to create configuration for each device connecting to the network, let’s draw out a map of the entire network as we intend to set it up:
# pele Network Map 10.55.0.0/16: - 10.55.0.0/24: servers - 10.55.0.1/32: DNS, HTTPS - 10.55.1.0/24: clients - 10.55.1.1/32: iPad Pro (la ta'orskami) - 10.55.1.2/32: iPhone XS (la selbeifonxa) - 10.55.1.3/32: MacBook (om)
Depending on free network space, it may be preferable to split the first /24 block up into two logical /25 blocks (
10.55.0.128/25). This is all a matter of taste and has no functional impact on the network. I’d suggest using consistent conventions in your subnetting whenever possible.
WireGuard requires a UDP port to be exposed to the outside world to work. A commonly used port for this is 51820. Depending on your network configuration, you may have to configure port forwarding. I cannot help you with this step if it is the case, however.
In case you ever need to test the UDP port forwarding, run the following on the machine you want to test:
$ nc -u -l -p 51820
And on another machine:
$ echo "hello, world" | nc -u <external IP> 51820
Run this command a few times in order to make sure the packets go through, as UDP is not inherently reliable. If you see at least one instance of “hello, world” on the machine you want to test, your port has been forwarded correctly. If not, contact whoever set up your network for help.
Now that you have all of the hard parts chosen, provision a new server running Alpine Linux and upgrade it to edge, then enable community and testing. Your /etc/apk/repositories file should look something like this:
# /etc/apk/repositories http://dl-3.alpinelinux.org/alpine/edge/main http://dl-3.alpinelinux.org/alpine/edge/community http://dl-3.alpinelinux.org/alpine/edge/testing
Upgrade all of the packages on the system and then reboot:
# apk -U upgrade # reboot
To install WireGuard and all of the needed tools, run the following:
# apk -U add wireguard-vanilla wireguard-tools
For those of you using other distributions, here is the version information from my WireGuard master:
luna [/etc/wireguard]# apk info wireguard-tools wireguard-tools-0.0.20190227-r0 description: Next generation secure network tunnel: userspace tools wireguard-tools-0.0.20190227-r0 webpage: https://www.wireguard.com wireguard-tools-0.0.20190227-r0 installed size: 20480 luna [/etc/wireguard]# apk info wireguard-vanilla wireguard-vanilla-4.19.30-r0 description: Next generation secure network tunnel: kernel modules for vanilla wireguard-vanilla-4.19.30-r0 webpage: https://www.wireguard.com wireguard-vanilla-4.19.30-r0 installed size: 352256
$ sudo add-apt-repository ppa:wireguard/wireguard $ sudo apt-get update $ sudo apt-get install wireguard
WireGuard uses strong cryptography for its protocol. As such you need to generate a private and public keypair. To generate them:
$ sudo -i # cd /etc/wireguard # wg genkey > pele-privatekey # cat pele-privatekey | wg pubkey > pele-publickey
Assuming your config file will be located at /etc/wireguard/pele.conf:
# /etc/wireguard/pele.conf [Interface] Address = 10.55.0.1/16 ListenPort = 51820 PrivateKey = <contents of file /etc/wireguard/pele-privatekey> PostUp = iptables -A FORWARD -i pele -o pele -j ACCEPT PostDown = iptables -D FORWARD -i pele -o pele -j ACCEPT
Save this and make sure only root can read any of these files:
# chown root:root /etc/wireguard/pele* # chmod 600 /etc/wireguard/pele*
On your iOS device, install the WireGuard app. Once it is installed, open it and do the following:
10.55.0.0/16in Allowed IPs
To add this client to the WireGuard server, add the following lines to the config file:
# /etc/wireguard/pele.conf # <snip from earlier> # la ta'orskami [Peer] PublicKey = <public key from iOS device> AllowedIPs = 10.55.1.1/32
Make sure the AllowedIPs range doesn’t allow for routing loops. It should be a /32 for any “client” devices and larger rangers for any “server” devices.
To test this, enable the WireGuard interface on the server side:
# wg-quick up pele # ping 10.55.0.1
If the pinging works, then your interface has successfully been brought online! In order to test this from your iOS device, enable the VPN connection in the WireGuard app, look for the latest handshake timer and open LibTerm. Run the following command:
$ ping 10.55.0.1
If this fails or you don’t see the connection handshake timer in the WireGuard app after enabling the connection, please be sure the UDP port is being properly forwarded. The version of netcat bundled into LibTerm is capable of running this test should you need to do that.
For convenience, we can add this to the system networking configuration so it starts automatically on boot. Add the following to your /etc/network/interfaces file:
auto pele iface pele inet static address 10.55.0.1 netmask 255.255.0.0 pre-up ip link add dev pele type wireguard pre-up wg setconf pele /etc/wireguard/pele.conf post-up ip route add 10.55.0.0/16 dev pele post-down ip link delete dev pele
And then reboot to make sure the configuration changes take hold. You will need to add additional
post-up ip route commands based on the AllowedIPs blocks for peers in your configuration; though this will be covered in detail when it is relevant.
To automatically start a WireGuard configuration located at /etc/wireguard/pele.conf on boot using systemd, run the following:
# systemctl enable wg-quick@pele # systemctl start wg-quick@pele
Reboot your box. After it comes back up, try and use the WireGuard tunnel. If it works, then you’re all good.
Please give me feedback on my approach to this. I also have a Patreon and a Ko-Fi in case you want to support this series. I hope this is useful to you all in some way. Stay tuned for the future parts of this series as I build up the network infrastructure from scratch. If you would like to give feedback on the posts as they are written, please watch this page for new pull requests.