Aug 29, 2022

Securely Tunneling WireGuard VPN's UDP Traffic

It is sometimes not possible to establish a VPN connection due to a country or an organization's restrictions and their counter-measures. This article outlines how UDP traffic can be routed through a proxy to encrypt data, enabling the connection of a UDP-based VPN, such as those using the WireGuard protocol.

I was recently faced with an issue of being unable to establish a VPN connection in a country that used Deep Packet Inspection (DPI) to examine datagrams and their common patterns.

My project started as a simple chat about VPN’s and how simply one could host their own at home, through the use of a small computer (such as a Raspberry Pi). I set up a VPN at home that very evening with an old Raspberry Pi, using a dynamic DNS service (my ISP did not provide a static IP) and a port-forward on my router (to make it reachable over the internet).

There was just one problem, I was traveling soon and I knew VPNs were hard to connect to, due to the use of DPI, at my destination. I set out to find a way to obfuscate the data, such as using a stunnel (tunnel over SSL), but that was slow and only worked with TCP. I opted not to use a VPN over TCP since OpenVPN is a very large codebase and is quite a bit slower, compared to WireGuard.

Proxy Concept

What if there was a proxy program that could intercept the datagrams, encrypt their data, then send it to a server which is running a similar proxy program to decrypt that data and send it to where it needs to be.

Typical WireGuard Flow

A and D are WireGuard peers.

WireGuard tunnel proxy flow

Proxied WireGuard Flow

A and D are WireGuard peers, they do not communicate directly over the internet, only the proxy servers communicate remotely with the encrypted data. B and C are the proxy programs running a server on each machine.

When B and C receive data from A and D, respectively, they encrypt and send it to each other, C and B, respectively.

When B and C receive data from each other, they decrypt it and send it to their local peers, A and D, respectively.

Implementation

I loosely say the term encrypt as a means to hide the VPN data from any packet inspection. WireGuard has its own cryptography implementations so we do not need to secure our data. We just need hide the WireGuard protocol pattern (e.g. type bits and reserved bits that are prepended to every WireGuard message) from anything that may be inspecting the data.

Server

Since the server cannot know the client’s address beforehand, it awaits a secret message to set its remote peer. Both the client and server know this secret message.

The server’s UDP handler flow looks like this:

When a UDP datagram is received:
    If it was sent from the locally running WireGuard server:
        Encrypt and send it to the client

    If it is the secret message:
        Set the client to the sender
        
    If it was sent from the client:
        Decrypt and send it to the locally running WireGuard peer

Client

The client knows the server’s address beforehand, so there is no need to negotiate it.

The client’s UDP handler flow looks like this:

When a UDP datagram is received:
    If it was sent from the locally running WireGuard server:
        Encrypt and send it to the server
        
    If it was sent from the server:
        Decrypt and send it to the locally running WireGuard peer

WireGuard Configurations

Server

# wg0.conf
[Interface]
PrivateKey = REDACTED
Address = 10.55.33.1/32,fd11:5ee:bad:c0de::1/64 # it is important to make the IP subnet mask /32 so it is one IP
MTU = 1420
ListenPort = 51820
### begin client1 ###
[Peer]
PublicKey = REDACTED
PresharedKey = REDACTED
Endpoint = 127.0.0.1:51821 # the local proxy server is running on this address
AllowedIPs = 10.55.33.2/32,fd11:5ee:bad:c0de::2/128
### end client1 ###

Client

# client1.conf
[Interface]
PrivateKey = REDACTED
ListenPort = 51820
Address = 10.55.33.2/24, fd11:5ee:bad:c0de::2/64
DNS = 1.1.1.1, 1.0.0.1
MTU = 1420

[Peer]
PublicKey = REDACTED
PresharedKey = REDACTED
AllowedIPs = 0.0.0.0/0
DisallowedIPs = 127.0.0.1/32, the.remote.server.address/32 # localhost and the remote server address
Endpoint = 127.0.0.1:51821 # the local proxy server is running on this address

You might notice that DisallowedIPs is not a valid WireGuard field. It’s purpose is to allow some IPs to not be routed through WireGuard. Using AllowedIPs and DisallowedIPs on this allowed IP calculator will give you a valid AllowedIPs field.

UFW Server Configuration

If you are using ufw on your server which also runs a WireGuard peer, you should run the following to get the expected behaviour (you may need sudo if you are not root).

ufw allow 51820/udp # not required unless you also want to connect without the proxy tunnel
ufw allow 51821/udp # 51821 is the port that your proxy server runs on

ufw route allow in on wg0 out on eth0
ufw route allow in on eth0 out on wg0

Conclusion

With the above concepts and configurations, one should be able to hide WireGuard traffic from any DPI and connect to their VPN. It is simple to create such UDP-forwarders with encrypt/decrypt capabilities and any custom obfuscation features that a developer wishes to implement. The resulting traffic appears to be random data with little to no identifying features. Additionally, added overhead for each datagram is up to the developer’s implementation of encryption and/or obfuscation.