Tunneling WireGuard over TCP with TunSafe

TunSafe supports tunneling the WireGuard UDP packets over a TCP connection. This is a more reliable protocol but suffers from worse performance. It's useful if UDP for some reason is unreliable or blocked.

How to use this

This feature was added recently so please download TunSafe 1.5-rc2 or later. It's not yet available on mobiles.

On the Clients, enable TCP by adding this to the Client's config file:


On the Server, enable listening on a TCP port. Note that the TunSafe Server supports both the traditional UDP protocol and TCP simultaneously.



We hate running one TCP implementation on top of another TCP implementation. There's problems with cascading retransmissions and head of line blocking, and performance is always much worse than a UDP based tunnel.

However, we also recognize that several users need to run WireGuard over TCP. One reason is that UDP packets are sometimes blocked by the network in corporate scenarios or in other types of firewalls. Also, in misconfigured networks outside of the user's control, TCP may be more reliable than UDP.

Additionally, we want TunSafe to be a drop-in replacement for OpenVPN, which also supports TCP based tunneling. The feature could also be used to run WireGuard tunnels over ssh tunnels, or through socks/https proxies.

The TunSafe project therefore takes the pragmatic approach of supporting WireGuard over TCP, while discouraging its use. We absolutely don't want people to start using TCP by default. It's meant to be used only in the extreme cases when nothing else is working.

We've added experimental support for TCP in the latest TunSafe master, which means you can try this out on Windows, OSX, Linux, and FreeBSD. On the server side, to listen on a TCP port, use ListenPortTCP=1234. (Not working on Windows yet). On the clients, use Endpoint=tcp:// The code is still very experimental and untested, and is not recommended for general use. Once the code is more well tested, we'll also release support for connecting to WireGuard over TCP in our Android and iOS clients.

To make the impact as small as possible to our WireGuard protocol handling, and to minimize the risk of security related issues, the TCP feature has been designed to be as self-contained as possible. When a packet comes in over TCP, it's sent over to the WireGuard protocol handler and treated as if it was a UDP packet, and vice versa. This means TCP support can also be supported in existing WireGuard deployments by using a separate process that converts TCP connections into UDP packets sent to the WireGuard Linux kernel module.

Each packet over TCP is prefixed by a 2-byte big endian number, which contains the length of the packet's payload. The payload is then the actual WireGuard UDP packet.

TCP has larger overhead than UDP, and we want to support the usual WireGuard MTU of 1420 without introducing extra packet "fragmenting". So we implemented an optimization to skip sending the 16-byte WireGuard header for every packet. TCP is a reliable connection, we know that sequence numbers are always monotonically increasing, so we can predict the contents of this header.

Here's an example:
A 1420 byte big packet sent over a WireGuard link will have 2 bytes of TCP payload length, 16 bytes of WireGuard headers, 16 bytes of WireGuard MAC, 20 bytes of TCP headers, and 40 bytes of IPv6 headers. This is a total of 1420 + 2 + 16 + 16 + 20 + 40 = 1514 bytes, exceeding the usual 1500 byte Ethernet MTU by 14 bytes. This means that a single full sized packet over WireGuard will result in 2 TCP packets. With our optimization, we reduce this to 1498 bytes, so it fits in one TCP packet.

Protocol specification

TT LLLLLL LLLLLLLL  [Payload LL bytes]
|  |
|  \-- Payload length, high byte first.
\----- Packet type

The packet types (TT) currently defined are:
TT = 00 = Normal   The payload is a normal unmodified WireGuard packet
                   including the regular WireGuard header.
     10 = Data     A WireGuard data packet (type 04) without the 16 byte
     ?1 = Reserved

When parsing an incoming Data (TT=10) packet, the Key ID and Counter from the most recently parsed WireGuard data packet (type 04) is prepended to the payload, with Counter incremented by 1.

This happens independently in each of the two TCP directions.


This implementation is a TunSafe specific and experimental extension to the protocol. We would love to find a variant of this proposal, or another solution that can provide the same functionality across other WireGuard implementations. We think a standardized way of doing two-factor authentication would be hugely beneficial to the WireGuard community. We're available in #tunsafe on Freenode to discuss this proposal further.