-
Notifications
You must be signed in to change notification settings - Fork 390
Surviving Active Probing
mtg with FakeTLS (ee-secrets) does a solid job hiding MTProto traffic from passive DPI. But modern censorship systems also perform active probing: they connect to suspected proxies themselves and check if the server behaves like the website it claims to be.
This page explains why proxies get detected and how to deploy mtg so that it survives both passive and active analysis.
A common misconception (see #458):
mtg "just closes the connection" on a failed handshake. That is not
true. When mtg receives a TLS ClientHello that does not pass MTProto
authentication, it performs domain fronting: it rewinds the bytes,
opens a TCP connection to the real host from the secret (or
domain-fronting.ip), and transparently relays traffic. The probe
therefore sees the real site's TLS handshake, certificate, and HTTP
responses.
The secret encodes a hostname — say www.vk.com. mtg announces this in
the TLS ServerName, and domain fronting connects to www.vk.com:443.
But the server itself sits at some Hetzner/OVH/DigitalOcean IP that has
nothing to do with VK.
Censors don't even need to probe: one DNS lookup for www.vk.com shows
that the IP doesn't match, and they block it.
Fix: Use a domain you own, with its DNS A/AAAA records pointing to the proxy server's IP. Then the match is natural.
mtg doctor checks this at validation time. Starting with the upcoming
release, mtg run also logs a warning at startup if a mismatch is
detected.
Even if the domain matches, an active probe that connects with a different SNI (or no SNI) should see something normal. If there is no real TLS server behind mtg, the fronting connection fails, and the probe sees a RST or timeout — suspicious for a host that claims to serve HTTPS.
Fix: Run a real web server (Caddy, nginx) on the same machine and put an SNI router (HAProxy, sslh, nginx stream) in front of everything on port 443. The router peeks at the ClientHello SNI:
- Your domain → mtg (FakeTLS)
- Anything else → real web server
A ready-made docker-compose setup is proposed in contrib/sni-router/
(see the open PR).
When mtg relays a ClientHello to the fronting domain, TLS is end-to-end between the probe and the real site. But the TCP layer (SYN, MSS, window scale, timestamps) comes from the proxy host's kernel, not from the fronting domain. A sophisticated censor can notice that the TCP stack doesn't match the expected OS of, say, vk.com's CDN.
This cannot be fixed in userspace. Mitigations:
# Normalize some TCP parameters (Linux)
sysctl -w net.ipv4.tcp_timestamps=1
sysctl -w net.ipv4.tcp_window_scaling=1
sysctl -w net.ipv4.tcp_sack=1
# Match a common MSS for your uplink MTU (usually 1460 for Ethernet)
iptables -t mangle -A POSTROUTING -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --set-mss 1460These don't make the fingerprint invisible but reduce the delta between the proxy host and a typical web server.
MTProto sessions are long-lived, low-bandwidth, and bursty — very different from normal HTTPS. There is no easy fix, but using the proxy through a SOCKS5 upstream (e.g. sing-box, xray) adds a hop that changes timing characteristics on the outbound side.
- Register a cheap domain (or use a subdomain you already own).
- Point its DNS to your VPS.
- Deploy mtg + HAProxy + Caddy (see
contrib/sni-router/in the open PR). - Generate the mtg secret with your domain:
mtg generate-secret --hex your.domain - Caddy obtains a Let's Encrypt cert automatically.
Result: browsers see a real website, Telegram clients get MTProto, DPI sees consistent SNI/IP/cert.
If the domain is behind Cloudflare (or another CDN that supports TCP passthrough / Spectrum):
- Cloudflare terminates the first TLS layer to the browser/probe.
- For MTProto traffic, configure Cloudflare Spectrum or a CNAME to forward port 443 TCP to your origin.
- mtg on the origin sees the FakeTLS ClientHello as usual.
This hides the origin IP entirely and leverages Cloudflare's reputation.
Caveat: Cloudflare Spectrum is a paid feature on most plans.
If you already run a web service on the VPS:
- Move the web service to an internal port (e.g. 8443).
- Add HAProxy or nginx stream on port 443 with SNI routing.
- Route your-domain to mtg, everything else to the web service.
No extra domain or certificate needed — just rearrange the ports.
# 1. Check SNI/IP match, DC connectivity, fronting reachability:
mtg doctor /path/to/config.toml
# 2. Simulate a probe — connect with a random SNI:
openssl s_client -connect YOUR_IP:443 -servername random.example.org
# You should see the real web server's certificate, not a connection reset.
# 3. Simulate a probe — connect with the correct SNI but no MTProto:
openssl s_client -connect YOUR_IP:443 -servername YOUR_DOMAIN
# Should see the same valid certificate (domain fronting kicks in).
# 4. Check from outside (optional):
curl -v https://YOUR_DOMAIN/
# Should return your web content with a valid cert.| Threat | Why | Possible mitigation |
|---|---|---|
| IP/ASN reputation block | The entire hosting provider is flagged | Move to a less-known ASN or use a CDN |
| Telegram client bugs | Broken ClientHello in old clients | Update to Telegram Desktop ≥ 6.7.2, Android ≥ 12.6.4 (tdesktop#30513) |
| Traffic analysis (flow correlation) | Long-lived encrypted streams with MTProto timing | No easy fix; SOCKS5 upstream adds indirection |
| Kernel TCP fingerprint | OS-level differences in SYN/ACK | Only partially mitigable (see above) |