Wireguard for the Initiated

Wireguard

All it Does

Wireguard creates a virtual network interface, the first of which is called wg0, which encrypts and decrypts traffic. The kernel retains a list of peers and associated public keys. If the kernel gets a packet destined to one of the peers, it gets encrypted and sent via UDP to the last known IP / port of that peer. The reverse happens if encrypted traffic arrives from a peer. Additionally, if a properly encrypted packet arrives from an unexpected IP, that peer is updated and all response packets will now flow back to that new IP. That’s how roaming is supported — just like mosh. That’s all it does. Everything else — complicated IP subnetting, routing or any fancy port mappings— is done with netfilter, iproute2 or other standard kernel strategies. Wireguard’s elegance is in doing only one thing — encrypting and decrypting traffic — really well.

Install

Get the package however you prefer. In Ubuntu you could:

apt install wireguard

Keys

Wireguard uses Curve25519 public / secret key pairs which can be generated with wg thusly:

wg genkey | tee secretkey | wg pubkey | tee publickey
# Machine A (1.1.1.1)
Private Key: EF7nGyEOl1OlZwoQgYLN41SQuDJTMpJeht9CMarHC1k=
Public Key: atv4BKui/BSG+Wz+3xZDvyqZi5fUsvZXqAyqo6JeaC8=
# Machine B (2.2.2.2)
Private Key: 8DIa2XfsMX7YqS8hH6DL3i0OXn3TrWNKKw8KCOQsPHs=
Public Key: AS5fnTaBkVjsu6MBj02tzrldSh0d9bEYvGJ3TjiHxj0=
# Machine A (1.1.1.1)
ip link add wg0 type wireguard
ip addr add 10.0.0.1/24 dev wg0
wg set wg0 private-key ./secretkey
ip link set wg0 up
# Machine B (2.2.2.2)
ip link add wg0 type wireguard
ip addr add 10.0.0.2/24 dev wg0
wg set wg0 private-key ./secretkey
ip link set wg0 up
# Machine B (2.2.2.2)
$ wg
interface: wg0
public key: AS5fnTaBkVjsu6MBj02tzrldSh0d9bEYvGJ3TjiHxj0=
private key: (hidden)
listening port: 50764

Peers

Now let’s tell machine “A” about the public key and initial IP / port combination to use to get to machine “B”:

# Machine A (1.1.1.1) <VPN 10.0.0.1>
wg set wg0 peer AS5fnTaBkVjsu6MBj02tzrldSh0d9bEYvGJ3TjiHxj0= allowed-ips 10.0.0.2/32 endpoint 2.2.2.2:50764
# Machine B (2.2.2.2) <VPN 10.0.0.2>
wg set wg0 peer atv4BKui/BSG+Wz+3xZDvyqZi5fUsvZXqAyqo6JeaC8= allowed-ips 10.0.0.1/32 endpoint 1.1.1.1:57593
# Machine A (1.1.1.1) <VPN 10.0.0.1>
$ ping 10.0.0.2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.489 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.519 ms
64 bytes from 10.0.0.2: icmp_seq=3 ttl=64 time=1.39 ms
^C
--- 10.0.0.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2029ms
rtt min/avg/max/mdev = 0.489/0.799/1.390/0.417 ms
# Machine A (1.1.1.1) <VPN 10.0.0.1>
$ wg
interface: wg0
public key: atv4BKui/BSG+Wz+3xZDvyqZi5fUsvZXqAyqo6JeaC8=
private key: (hidden)
listening port: 57593
peer: AS5fnTaBkVjsu6MBj02tzrldSh0d9bEYvGJ3TjiHxj0=
endpoint: 2.2.2.2:50764
allowed ips: 10.0.0.1/32
latest handshake: 2 minutes, 15 seconds ago
transfer: 1.09 KiB received, 11.18 KiB sent

That’s it (Oh, one more thing!)

There is a helper app called wg-quick which reads config files from /etc/wireguard/ which makes setting things up a little easier. (and more painlessly survive reboots) Here’s a minimal example of a wg0.conf:

# Machine A (1.1.1.1) <VPN 10.0.0.1>
# /etc/wireguard/wg0.conf
[Interface]
PrivateKey = EF7nGyEOl1OlZwoQgYLN41SQuDJTMpJeht9CMarHC1k=
Address = 10.0.0.1/24
ListenPort = 57593
SaveConfig = false
[Peer]
PublicKey = AS5fnTaBkVjsu6MBj02tzrldSh0d9bEYvGJ3TjiHxj0=
AllowedIPs = 10.0.0.2/32
wg-quick up wg0
systemctl enable wg-quick@wg0
# Machine B (2.2.2.2) <VPN 10.0.0.2>
# /etc/wireguard/wg0.conf
[Interface]
PrivateKey = 8DIa2XfsMX7YqS8hH6DL3i0OXn3TrWNKKw8KCOQsPHs=
Address = 10.0.0.1/24
ListenPort = 57593
SaveConfig = false
[Peer]
PublicKey = atv4BKui/BSG+Wz+3xZDvyqZi5fUsvZXqAyqo6JeaC8=
AllowedIPs = 10.0.0.1/32
EndPoint = 1.1.1.1:57593

DNS

If you end up routing most or all traffic through a Wireguard tunnel, you might want to automatically drop the IP of a DNS cache in there and maybe set a search domain. You can do that fairly concisely with:

# Machine A (1.1.1.1) <VPN 10.0.0.1>
# /etc/wireguard/wg0.conf
[Interface]
PrivateKey = EF7nGyEOl1OlZwoQgYLN41SQuDJTMpJeht9CMarHC1k=
Address = 10.0.0.1/24
ListenPort = 57593
SaveConfig = false
DNS = 8.8.8.8, example.com
[Peer]
PublicKey = AS5fnTaBkVjsu6MBj02tzrldSh0d9bEYvGJ3TjiHxj0=
AllowedIPs = 10.0.0.2/32

Hub and Spoke

It is common to designate one peer as a “server” and have other clients peer through that to get to other peers. You do this using traditional iproute2 strategies (echo 1 > /proc/sys/net/ipv4/ip_forward and ip route entries) so there isn’t anything special here. I’ve been very happy with the minimal amount of CPU encrypting / decrypting to accomplish this costs. Even a $200 embedded appliance holds up fairly well at the center. You can see what I use for this in A Home Network which is a bit more broad article.

Conclusion

Wireguard is one of those “insanely great” things. It just does what it says on the can and nothing else. You can coax it to sending keepalive packets every once in a while (just add persistent-keepalive 25 to the wg command or PersistentKeepalive = 25 to the wg0.conf file to get a packet every 25 seconds) but beyond that it is totally silent unless traffic is requested. It handles roaming clients the way you want — just picks up where you left off if the packets are encrypted properly with the correct sequence number. And it doesn’t try to re-solve problems — all routing or interesting network configurations are handled the standard iproute2 way. It is the breath of fresh air you need if you are still stuck in IPSec / PPP / CHAP authentication hell.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Anders Brownworth

Anders Brownworth

Applied CBDC Research — formerly USDC @ Circle.com, Bandwidth.com. MIT / Podcaster / Runner / Helicopter Pilot