Skip to content

WireGuard Primer

Published: at 01:00 AM

Introduction

WireGuard 1 is a simple, fast, and modern VPN that utilizes cryptography.

It encapsulates IP packets over UDP. The general workflow can be summarized as:

It’s similar to the ssh model. Both parties have each other’s public keys and they communicate by exchanging packets encrypted by the public keys through the WireGuard interface.

About the WireGuard network interface

The WireGuard network interface is where the magic happens and it acts as a tunnel interface.

You can add one or more such interfaces depending on your needs. It can be configured normally using utilities like ifconfig(8) or ip-address(8), but more typically you configure such interfaces using the wg(8) tool for WireGuard-specific settings.

By saying that it acts as a tunnel interface, we mean:

The above short, simplified summary is for our understanding purpose and there’s much happening behind the scenes to provide proper privacy, authenticity, and secrecy.

About Cryptokey Routing

Cryptokey Routing is at the heart of WireGuard.

It associates public keys with a list of tunnel IPs that are allowed inside the tunnel. Those public keys correspond to a list of peers, each peer has a public key. We use public keys to authenticate each other’s identity. They can be safely passed around for use in plaintext config files by any out-of-band method.

It also associates a private key with a WireGuard network interface.

For example, a server may have below config file:

[Interface]
PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
ListenPort = 51820

[Peer]
PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
AllowedIPs = 10.192.122.3/32, 10.192.124.1/24

[Peer]
PublicKey = TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=
AllowedIPs = 10.192.122.4/32, 192.168.0.0/16

[Peer]
PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=
AllowedIPs = 10.10.10.230/32

Each peer in this case will be a client that can send packets to the server’s network interface with a source IP matching the allowed IPs.

For example, if a packet is received by the server from peer gN65BkIK..., after being decrypted and authenticated, if its source IP is 10.10.10.230, then it’s allowed onto the interface; otherwise it’s dropped. If the server wants to send a packet to a client, it looks at that packet’s destination IP and compares it to each peer’s list of allowed IPs to see which peer to send it to. For example, if the server’s network interface is asked to send a packet with a destination IP of 10.10.10.230, it will encrypt it using the public key of peer gN65BkIK..., and then send it out to the peer’s most recent internet endpoint.

In the meantime, a client may have the below config file:

[Interface]
PrivateKey = gI6EdUSYvn8ugXOt8QQD6Yc+JyiZxIhp3GInSWRfWGE=
ListenPort = 21841

[Peer]
PublicKey = HIgo9xNzJMWLKASShiTqIybxZ0U3wGLiUeJ1PKf8ykw=
Endpoint = 192.95.5.69:51820
AllowedIPs = 0.0.0.0/0

The client here has only 1 peer with public key HIgo9.... That peer is able to send packets to this interface with any source IP.

The endpoint in the above client config is an initial endpoint of its single peer so that it knows where to send encrypted data before it has received encrypted data. Compared to this, the server side config does not have any initial endpoints of its peers because the server discovers the peer endpoints by examining from where correctly authenticated data originates.

If a server changes its own endpoint, and sends data to the clients, the clients will discover the new server endpoint and update its config. This is what we referred in the previous discussion “use the most recent IP endpoint”. Such feature is also called IP roaming.

For example, if a packet is received from peer HIgo9..., after being decrypted and authenticated, it is allowed onto the interface; otherwise it is dropped. In the meantime, if the client’s network interface is asked to send a packet to its single peer, it will encrypt the packet for the peer with any destination IP since the allowed IP is a wildcard 0.0.0.0/0, and then send it out to the single peer’s most recent internet endpoint.

In other words, the list of allowed IPs behave differently depending on the purpose is sending or receiving:

This is important and keep these two rules in mind!

Combining the public key and the bi-purpose list of allowed IPs is what we call a CryptoKey Routing Table: the simple association of public keys and allowed IPs.

Note that any of the IP field can be either IPv4, IPv6, or any combination of them.

The tight coupling of peer identity and the allowed IPs ease system admins from complicated firewall extensions but rather using a simplified match on “is it from this IP? on this interface?“.

Setup

Installation 2

Here I only cover the commands used to install WireGuard in Ubuntu.

sudo apt install wireguard

Verify your installation by checking its version:

wg -v

Verify that the wireguard kernel module is loaded:

lsmod | grep wireguard

If not loaded, try to reboot your instance and check again.

You can manually load it with:

sudo modprobe wireguard

Server Setup 3

Generate a public and private certificate on the server:

umask 077
wg genkey | tee server_private_key | wg pubkey > server_public_key

Create the server config file in /etc/wireguard/wg0.conf:

[Interface]
Address = 10.9.0.1/24, fd42:42:42::1/64
SaveConfig = true
PrivateKey = # PLACEHOLDER for your server side private key
ListenPort = 51820
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; ip6tables -A FORWARD -i %i -j ACCEPT; ip6tables -A FORWARD -o %i -j ACCEPT; ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; ip6tables -D FORWARD -i %i -j ACCEPT; ip6tables -D FORWARD -o %i -j ACCEPT; ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
PublicKey = # PLACEHOLDER for your client side public key
AllowedIPs = 10.9.0.2/32, fd42:42:42::2/128

Note that you need to change the public interface based on your situation. In my case it is eth0. If it is not, change it to the actual name.

As we mentioned earlier, the list of AllowedIPs has different interpretation when sending/receiving packets (a routing table when sending and a ACL when receiving). You will want to add your LAN’s subnet under AllowedIPs so that you can access them through the tunnel. Note that it is a subnet! Otherwise you may encounter errors like RTNETLINK answers: File exists.

In the above sever config file, I also assigned an IPv6 IP for the server and the peer since my instance has a public IPv6 address, and hence we can use it as a dual-stack VPN.

Enable IPv4/IPv6 forwarding

Open /etc/sysctl.conf and uncomment the following lines:

net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1

Restart the server or use the following commands to let the forwarding to take effect.

sudo sysctl --system

Start WireGuard

sudo chown -v root:root /etc/wireguard/wg0.conf
sudo chmod -v 644 /etc/wireguard/wg0.conf
wg-quick up wg0
sudo systemctl enable --now [email protected]

Client Setup

I’m using the WireGuard Mac App, and the configuration is simple and intuitive. Follow the instructions above to generate the certificates for the client, and populate the configs following the official docs.

Forward all your traffic through the tunnel 4

Remember what we emphasized earlier? The dual-purpose list of AllowedIPs? If you want your traffic looks like it’s coming from your server, you can forward all the traffic through WireGuard interface.

Simply change the AllowedIPs line on the client to this:

AllowedIPs = 0.0.0.0/0, ::/0

This will make the wg0 (or whatever your WireGuard interface is) responsible for routing all IP addresses/traffic over your server. You can check this by visiting ping.pe.

Forward all IPv6-traffic through the tunnel

I want to route all my IPv6 traffic through the tunnel but keep the IPv6 traffic untouched. At first I tried setting:

AllowedIPs = 10.9.0.1/32, ::/0

But it seems WireGuard also changed the routing table for IPv4 (checked via netstat -nr). I found this reddit post and the workaround is:

AllowedIPs = 10.9.0.1/32, ::/1, 8000::/1

It’s a clever hack to make it work but I expect a cleaner solution should exist (like the previous config).

Now I can reach IPv6-only websites like bt.byr.cn while not tunneling my IPv4 traffic to the slow VPN network!

Protect your DNS 5

When you use WireGuard in your machine as a client, your local network won’t be accessible, which means if the DNS servers pushed by your DHCP server are in the local network, you cannot access it! You can add a DNS entry in your client interface config like below:

[Interface]
PrivateKey = (hidden)
Address = 10.9.0.2/24, fd42:42:42::2/64
DNS = 176.103.130.130, 176.103.130.131

[Peer]
PublicKey = # PLACEHOLDER for your peer's public key
AllowedIPs = 10.9.0.1/32, fd42:42:42::1/128
Endpoint = # PLACEHOLDER for your peer's remote endpoint, be it IPv4 or IPv6

Here we use Adguard DNS but you can definitely host your own DNS server in your server machine. I also recommend NextDNS for your DNS experience. Thank you.

Footnotes

  1. https://www.wireguard.com/

  2. https://www.wireguard.com/install/

  3. http://portal.altispeed.com/kb/faq.php?id=201

  4. https://www.stavros.io/posts/how-to-configure-wireguard/

  5. https://stanislas.blog/2019/01/how-to-setup-vpn-server-wireguard-nat-ipv6/