WireGuard over TCP

WireGuard over TCP

Starting from eduVPN version 3.x, we added WireGuard support in addition to OpenVPN. WireGuard brings a number of improvements over OpenVPN:

  • Smaller codebase, easier to understand, debug and validate
  • Support for modern crypto protocols only
  • Better throughput (https://www.wireguard.com/performance/)
  • Support in the Linux kernel

However, a problem with WireGuard is that it is more prone to be blocked compared to OpenVPN. Some networks only allow traffic using TCP (HTTP/HTTPS traffic, normal web traffic) and therefore block WireGuard which is UDP only. OpenVPN has support for both UDP and TCP which makes it less likely to be blocked.

In this blog post, we explain what led us to write ProxyGuard, a way to run WireGuard over HTTP(S).

WireGuard explicitly does not support tunneling over TCP, due to the classically terrible network performance of tunneling TCP-over-TCP. https://www.wireguard.com/known-limitations/

They further suggest that TCP support should be added by an application that wraps the UDP packets:

Rather, transforming WireGuard's UDP packets into TCP is the job of an upper layer of obfuscation (see previous point), and can be accomplished by projects like udptunnel and udp2raw.

We think this makes sense as it keeps the WireGuard codebase short and simple.

In order to better accommodate our use case, we had a look at the mentioned tools but found that they have one or several of the following limitations:

  • Not actively developed
  • Only work for users with administrator privileges
  • Very complex codebase that is hard to understand
  • Not written in Go, we would favor an implementation in Go, enabling us to integrate it with our existing Go library for eduVPN: https://github.com/eduvpn/eduvpn-common. Whilst we can integrate tools written in other languages in a Go codebase, it does make it more complicated.

We therefore decided to start writing our own simple Go program that has one job: convert UDP packets to TCP and vice-versa. As this application's main goal is to Proxy WireGuard, we called it: ProxyGuard.

The primary concept is illustrated below, with the blue boxes representing traffic that goes over the internet:

ProxyGuard works with a client and server approach. The client is responsible for converting the local WireGuard traffic to TCP and send incoming TCP packets back to WireGuard. The server has the reverse role, where incoming TCP packets are converted to UDP and the reverse for the other direction. Details on how the conversion between UDP and TCP packets is done can be read on the ProxyGuard docs: https://codeberg.org/eduVPN/proxyguard/src/branch/main/technical.md.

This concept proved to be effective and was utilized in our first few releases of ProxyGuard.

However, in practice we experience another problem that we want to fix. Most eduVPN setups have the eduVPN user portal and the WireGuard socket running on the same machine. If we want to use TCP port 443, the port used for web traffic that is less likely to be blocked, we have to resort to convoluted setups: https://docs.eduvpn.org/server/v3/port-sharing.html. This problem is currently there for the OpenVPN setups too. If we can resolve this issue, we can in the future use ProxyGuard with OpenVPN too.

Therefore, we wanted ProxyGuard to be put behind a reverse proxy such as Apache or Nginx, so we could have everything running on the same port. And maybe even allow someone to completely close off the non-web ports, which would allow someone to run an eduVPN server with only 2 ports open: 80+443 (or 3 for SSH).
To facilitate this idea, we tried WebSockets (https://datatracker.ietf.org/doc/html/rfc6455) using a Go library. This approach also seemed to work. However, the challenge is that we now have to deal with another protocol on top of TCP. This adds overhead and complexity.

In the end, we came up with a solution that does a handshake like WebSockets but then uses the normal TCP connection that is established. It works by first sending an ordinary HTTP request to the server (e.g. through Apache) and then "upgrading" the connection to a different protocol. We call this protocol: UoTLV/1 (UDP over TCP Length Value Version 1). This protocol is the same protocol that handles UDP to TCP conversion as our first version.

The following figure is how it all ties together:

So the only change that needs to be made in a reverse proxy is a rule to forward this "Upgrade" traffic to ProxyGuard and the rest to the VPN portal, for eduVPN that is vpn-user-portal: https://codeberg.org/eduVPN/vpn-user-portal. For further information on how to configure this with eduVPN, see the eduVPN docs: https://docs.eduvpn.org/server/v3/wireguard.html#web-server-configuration

This approach also gives us the following advantages "for free":

  • Better obfuscation due to allowing for proxying over TLS
  • Ability to negotiate the protocol that is being used, if we update the ProxyGuard packet structure, we can simply update the HTTP header that is used in the handshake and the ProxyGuard Server checks the protocol that is used
  • Better security/isolation: ProxyGuard Server is not *directly* exposed to the internet
  • Ability for ProxyGuard Server to run completely without any extra privileges as the reverse proxy listens on the privileged web port

This approach seems unique to us as most tools are focused on using complex protocols, e.g. there are tools that proxy WireGuard using the aforementioned WebSockets protocol (https://github.com/erebe/wstunnel) or tools that tunnel UDP over encrypted TCP connections (https://github.com/wangyu-/udp2raw). We think that the solution we came up with covers the use cases for eduVPN well and it has a good tradeoff between simplicity and level of obfuscation.

To test ProxyGuard yourself, you can run the tool as a standalone binary on a client and server (https://codeberg.org/eduVPN/proxyguard/releases/tag/1.0.1). Or test the support in upcoming eduVPN client releases (https://github.com/Amebis/eduVPN/releases/tag/3.255.18 and https://github.com/eduvpn/python-eduvpn-client/releases/tag/4.2.99.1) with an eduVPN server that supports it https://docs.eduvpn.org/server/v3/wireguard.html#wireguard-over-tcp

Skip to content