In 2021, the WireGuard VPN protocol became so popular in Egypt that it was honored to be included in the block list, indescribably “delighting” not only customers of Cloudflare Warp+, Mullvad Wireguard and other commercial VPN providers, but also some corporate VPN users. Preliminary research has shown that DPI targets WireGuard Handshake Initiate packets that have a fixed size (148 bytes) and a recognizable structure (the first four bytes of a UDP packet [0x01, 0x00, 0x00, 0x00]).
To understand how DPI can detect and block WireGuard, you need some theory. The WireGuard protocol itself is elementary, the traffic is packed in quite typical UDP with the addition of a small header. To negotiate a WireGuard tunnel, as a rule, two (three, if you count Keepalive) packets are sufficient:
- The side that wants to establish the tunnel (the client) sends the Handshake Initiation to the other side (the server).
- If the server is currently ready to accept the connection, it replies to the client with a Handshake Response packet.
- The client receives a Handshake Response and responds with a Keepalive packet, which is a UDP packet containing only WireGuard MessageTransportType(4) header. Subsequently, the keys are updated by repeating the described procedure approximately every two minutes.
Thus, it seems that to block WireGuard DPI, it is enough to monitor UDP packets with a size of 148 bytes and check the first four bytes in them for the known signature [0x01, 0x00, 0x00, 0x00]. However, it is worth noting that corporate implementations of WireGuard can use the reserved three bytes (the Reserved field) for their own needs (for example, in Jamf Private Access they are used as a session identifier). In addition, it is possible that eventually, they will be used in the official client. So for greater accuracy, it makes sense to limit to only the first byte of a UDP packet. On the other hand, blocking all 148-byte UDP packets with the first byte 0x01 looks rather risky. The same is true for the Handshake Response packet, which also has a fixed size (92 bytes) and a similar signature with three reserved bytes [0x02, 0x00, 0x00, 0x00].
What then might a possible criterion for blocking WireGuard with a minimum probability of false positives look like? Apparently, DPI should skip a packet “similar” to a Handshake Initiate, remember the session state (IP addresses and ports) and block only after a response packet “similar” to a Handshake Response. It is impossible to say for sure, but by indirect signs it is very likely that we will be dealing with something similar.
Few words on where this story begone. Most likely, I would not specifically deal with this topic if I were suddenly not interested in a forum post in which a user described a curious way to bypass WireGuard blocking using another VPN. If interested, you can read our correspondence. But in short, he used Windscribe VPN to activate the WireGuard tunnel, then turned off Windscribe VPN and continued to use WireGuard (official client with Cloudflare Warp + configuration) as if it was not blocked at all.
During our communication, other details were gradually revealed, including the state, which in such an original way “pushes” the population to a more in-depth study of network technologies. The last time I was in Egypt was in 2010, and I cannot say that I miss it much. However, currently it is one of the few easily accessible countries, and lately, I have been seriously thinking about flying there for diving. Needless to say, the inability to access home networks would be a very unpleasant surprise.
The next step was to decide what to use to mask the Handshake Initiation and Handshake Response, instead of Windscribe VPN. Of course, we can build a self-written inexpensive service to encapsulate and forward these packets, and the load on it would be minimal (two packets every two minutes per tunnel). However, there has been a more suitable candidate for this role for quite some time now, and his name is SOCKS5. The fifth version of this popular protocol supports UDP, and this is what we need.
Earlier, I wrote a post about my VPN client project based on Cloudflare’s implementation of the WireGuard protocol. So, I decided to add SOCKS5 support to it. Worth to note, that in the process of testing intermediate assemblies, some features of the DPI implementation were revealed. For example, if the first handshake initiation missed SOCKS and hit DPI, then whatever you send later in that UDP session, DPI will permanently block it. It is curious that a small part of the packets somehow still breaks through (perhaps their route bypass DPI). The opposite might also be true, if the UDP session did not start with Handshake Initiate – Handshake Response, then DPI won’t block this session, even if these packets appear there in the future. This could explain why the user tunnel did not break after two minutes on the next Handshake Initiation. However, this is only an unconfirmed hypothesis.
The following optional parameters have been added to support SOCKS5 in Wiresock VPN Client:
- Socks5Proxy – specifies SOCKS5 proxy endpoint, e.g.
Socks5Proxy = socks5.sshvpn.me:1080or
Socks5Proxy = 18.104.22.168:1080
- Socks5ProxyUsername – specifies SOCKS5 username (optional)
- Socks5ProxyPassword – specifies SOCKS5 password (optional)
We also needed a SOCKS5 server with UDP ASSOCIATE support for the testing. I believe most readers are quite capable of configuring such a server on their own, so I think not to inflate the size of the post by giving a detailed design here. For the rest, I can address the memo that I created for myself while installing Dante from Inferno Nettverk A/S on Ubuntu 20.04 in Oracle Cloud.
We have tested SOCKS5 tunneling for Handshake Initiate/Response packets only and also for all WireGuard tunnel packets of. Both options did an impressive job with blocking, so we settled on the first one, as it is more beneficial concerning performance and resources. Of the unexpected side effects, sending Handshake Initiate through a SOCKS5 proxy “fixed” the ability to build a nested WireGuard tunnel using the official WireGuard for Windows (as an external tunnel) and the Wiresock VPN Client (as an internal one), which was “broken” sometime between releases 0.5 and 0.5.3. The official client suddenly stopped letting Handshake Initiate into the tunnel (maybe this is due to work on supporting its own nested tunnels), but after wrapping it in SOCKS, it stopped recognizing it and everything worked again.
I also wanted to note that it is not very difficult to make a dedicated Windows application using Windows Packet Filter for the official WireGuard client, which will intercept outgoing Handshake Initiate and send them via SOCKS5. If this is in demand, then I will try to find time for implementation.
That’s all for now. Hope it helps!