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.
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.