Tunneling WireGuard over TLS using SNI Domain Fronting

There are numerous ways to get unrestricted egress on a restricted network. Here I will demonstrate how to use socat to tunnel a UDP connection over a TLS tunnel with a faked SNI domain in order to bypass network restrictions.

This technique works on a restricted network that allows outbound TLS traffic to at least a single domain, but only checks the domain in the TLS Client Hello SNI field, and not the destination IP address. I have found this to be a common setup on many captive portal or restricted networks making use of a DPI firewall to block all other network traffic.

Once an UDP tunnel is established, a UDP VPN such as WireGuard can be used to route all traffic unrestricted.

In order to test if this strategy would work on a restricted network, make a request to a HTTPS server with fake sni information and verify that you get a response from the server you make the request to. Not all servers send a HTTP response for host’s they are not responsible for, but most seem to. Even if its an error message, that’s enough to verify that the connection successfully egressed the network and reached the destination server. I’ve found Cloudflare’s 1.1.1.1 does exactly this.

Use the following command replacing SNI_DOMAIN with the domain you want to test with. Remember, the SNI_DOMAIN needs to be a domain that the network allows HTTPS connections to. If the intended server responds, like the Cloudflare example below, then this method will work.

$ curl -vk --resolve $SNI_DOMAIN:443:1.1.1.1 "https://$SNI_DOMAIN"
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>cloudflare</center>
</body>
</html>

Server

The server only needs to be able to receive inbound network traffic on port 443. Any cheap VPS will do. A WireGuard server is also needed and can run on the same VPS. Setting up a WireGuard server is outside the scope of this post.

Creating a SSL Certificate

The server will need a TLS certificate to use for the TLS connection. Since the SNI information will be fake, and the tunneled UDP connection will verify server/client keys. The security of this certificate does not matter too much. But be sure to replace the subj parameters with the desired values.

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 3650 -nodes -subj "/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=CommonNameOrHostname"

Keep the key.pem and cert.pem files safe for use with socat.

Socat Server

The socat server is what listens for inbound TLS connections and forwards the tunneled UDP traffic to the desired host.

Set the following variables:

  • UDP_DEST - Destination host to relay UDP packets to. This will be the address of the WireGuard Server.
  • UDP_DEST_PORT - Destination port for UDP packets. Often 51820 for WireGuard.
socat -d \
  OPENSSL-LISTEN:443,fork,reuseaddr,keepalive,cert="cert.pem",key="key.pem",verify=0,su=nobody,nodelay \
  UDP-SENDTO:"$UDP_DEST:$UDP_DEST_PORT"

Client

The client is made up of the socat client and WireGuard client which connects to the server through the socat tunnel.

Socat Client

The socat client runs on the client device on the restricted network. It connects to the socat server establishing a TLS connection with a fake SNI domain to trick the firewall to allow the traffic.

Set the following variables:

  • SERVER - The IP or hostname of the server that runs the socat listener.
  • SNI_DOMAIN - The domain to fake traffic to. Set this to an allowed domain to appear in the SNI field of the ClientHello packet.
  • UDP_LISTEN_PORT - UDP port to listen on locally for traffic to relay. Set so something like 51820 if using WireGuard.
socat -d -t10 -T10 \
  UDP4-LISTEN:"$UDP_LISTEN_PORT",fork,bind=127.0.0.1 \
  OPENSSL:"$SERVER":443,verify=0,snihost="$SNI_DOMAIN",keepalive,nodelay

Remove bind=127.0.0.1 to have the socat client listen for UDP traffic on all interfaces.

Optionally, socat can verify the server’s certificate, but that is outside the scope of this post and not needed if the tunneled connection does the verification, which is the case for WireGuard.

WireGuard Client

Set the WireGuard MTU to 1280 to account for the smaller packet size allowed due to the overhead of TLS tunneling.

Traffic destined to the socat SERVER will need to be routed directly and not over the WireGuard interface. This can be achieved my setting the AllowedIPs in the WireGuard config to exclude the IP of the socat server. If the socat client is on a different host than the WireGuard client, the socat client’s IP will need to be excluded from AllowedIPs as well.

Tools like the WireGuard AllowedIPs Calculator can generate the AllowedIPs section of the WireGuard config to exclude a few select IPs and route everything else through the tunnel.

Note: other services that may be listening on other ports the server will likely still be inaccessible.

Results

Tunnel Speedtest: tunnel speedtest

Normal Speedtest: un-tunnel speedtest

It’s not pretty, but it works.

EDIT 2025-05-28: Added information for testing with curl and WireGuard AllowedIPs.