Changelog¶
4.15.0¶
- ClientHello JA4 fingerprint distribution on
/statsand/metrics(#102, follow-up to #39 / #101). When TSPU updates a detection signature, the only operationally interesting traffic is the probe that fails secret validation — so the proxy now hashes every well-formed ClientHello (per the foxio JA4 spec) before the HMAC check and rolls it into a top-32 counter. Surfaces asja4_seen<TAB><hash><TAB><N>tab-separated lines in/statsandteleproxy_ja4_seen{hash="..."}samples in/metrics. Always on, no configuration. Optional[stats] ja4_log = trueTOML key (also--ja4-logCLI /JA4_LOG=trueenv) printsja4=<hash> sni=<name>per connection at verbose level 2 for one-off investigations. See the new ClientHello JA4 Fingerprint Distribution section. Downstream goal: the observatory project can now chart fingerprint distribution over time and correlate with block rate instead of relying on user reports.
4.14.1¶
- Fix macOS backend-forward path. Backend forwarding (the camouflage fallback for invalid handshakes — wrong secret, stale timestamp, unknown SNI, duplicate client_random, plain HTTPS probes from a browser) silently dropped every request on macOS native builds:
writev()to a freshly opened outbound socket whoseconnect()was still inEINPROGRESSreturnsENOTCONNon BSD/Darwin where Linux returnsEAGAIN, and the proxy treated anything other thanEAGAIN/EINTRas fatal — tearing down the connection before the SYN-ACK arrived.ENOTCONNis now a retry condition too. Five fake-TLS E2E tests that were previously silently passing in Linux Docker but failing on the macOS dev build (test_wrong_secret_rejected,test_stale_timestamp_rejected,test_unknown_sni_falls_back,test_duplicate_client_random_rejected,test_browser_tls_sees_real_backend) now pass on both targets. The bug never manifested in production because production runs Linux. - Disable knob for the v4.14.0 MSS clamp. New
mss_clamp = falseTOML key,--no-mss-clampCLI flag, andMSS_CLAMP=falseenv var (start.sh) turn off the automatic ClientHello fragmentation shipped in v4.14.0. Default stays on. Useful escape hatch for bandwidth-bound operators willing to take the JA4 detection risk in exchange for ~5× lower packet count on the proxy listener. See Forced ClientHello fragmentation for what's being turned off.
4.14.0¶
- Automatic ClientHello fragmentation against TSPU JA4 fingerprinting (#39). Russian DPI (TSPU/ASBI) classifies traffic to fake-TLS MTProxies by JA4-hashing the client's ClientHello — a fingerprint teleproxy itself can't change because the bytes are produced by the Telegram client. But teleproxy can change how those bytes hit the wire: the public listening socket now announces a 256-byte TCP MSS in the SYN-ACK, and the client kernel respects it, splitting the outgoing ClientHello (~500-700 bytes) across 2-3 TCP segments. Critically, ALPN and
signature_algorithms— both required inputs to the JA4 hash — typically land in segment 2 or 3 of a real Telegram ClientHello, so a DPI that fingerprints from the first segment alone computes the wrong hash and the connection slips past the signature. The trick is automatic — no flag, no config, no upgrade-time decisions for the operator — and is scoped to the user-facing proxy port only; the HTTP/stats//metricslistener keeps full system MSS. Pairs with the existing ServerHello segmentation so both sides of the handshake become unfriendly to single-packet DPI. Genuinely novel — neithermtgnortelemtdoes this. Trade-off: Linux applies the listening-socket MSS to both directions of each accepted connection, so server→client segments are also capped at 256 bytes — packet count rises ~5× and per-byte TCP/IP header overhead grows from ~3% to ~15% on the proxy listener. Modern hardware with TSO/GSO handles the CPU side, and the existing 20MB MTProto E2E test still completes well inside its timeout. See the new Forced ClientHello fragmentation section for the mechanism and rationale.
4.13.0¶
- Wildcard certificate support in fake-TLS (#44). Operators with a wildcard cert like
*.example.comserved from a concrete vhost (e.g.proxy.example.com) couldn't make fake-TLS work — SNI lookup did literal string compare against*.example.com, every real ClientHello missed the match, and the connection landed on nginx's default vhost serving an unrelated cert (the failure mode also reported against mtg as 9seconds/mtg#394). Configure-D '*.example.com:proxy.example.com:443'(orEE_DOMAIN=*.example.com,EE_BACKEND=proxy.example.com:443) and teleproxy now matches any single-label subdomain against the pattern per RFC 6125:proxy.example.commatches,example.com(apex) anda.b.example.com(multi-label) do not. Startup fingerprinting probes the backend hostname, so the captured ServerHello parameters reflect the real cert vhost. Covered bymake test-wildcard-cert. - Document direct-mode limitations (#79). The Direct-to-DC Mode page now spells out what
--directtrades away by skipping Telegram's middle-end: media on non-Premium accounts may not load, sponsored channels aren't delivered, and Telegram voice/video calls aren't carried by any MTProto proxy regardless of mode. Answers the recurring symptom in #60. CONFIG_DOWNLOAD_PROXYenv var for the proxy-multi.conf download (#61). Hosts that can't reachcore.telegram.orgdirectly can now route the config refresh through an outbound HTTP or SOCKS proxy. Accepts any URLcurl -xunderstands (http://,https://,socks5://,socks5h://, with optionaluser:pass@). Falls back toSOCKS5_PROXYwhen unset, so users who already proxy DC traffic don't need to set anything new. Applies to the initial fetch instart.shand the 6-hour cron refresh.- Split SNI domain from camouflage backend (#62). The fake-TLS feature used to require
EE_DOMAINto be both the SNI hostname and the backend connect target — operators worked around it by editing/etc/hosts. NewEE_BACKENDenv var separates the two:EE_DOMAINkeeps a clean public SNI name likecloudflare.com, whileEE_BACKENDpoints at the actual backend (127.0.0.1:8443,[::1]:8443, orunix:/run/nginx.sock). Reality-style. Available as TOML too:domain = [{ name = "cloudflare.com", backend = "127.0.0.1:8443" }]. The legacyEE_DOMAIN=host:portanddomain = "..."strings still work unchanged. - Fix pre-handshake socket accumulation (#63). Scanners and probes that opened a TCP connection but never sent the obfs2 header used to sit on the proxy until OS-level TCP keepalive killed them roughly two hours later, inflating
total_connections(Prometheusteleproxy_active_connections) far above the authenticated user count. The proxy already schedules a 10-second handshake alarm, but the handler used to no-op when fake-TLS wasn't configured. It now drops the unhandshaked socket. Most visible under random-padding (dd) mode, where there's no SNI gate to filter scanners early. Newmake test-handshake-timeoutregression test asserts the counter returns to baseline within 15 seconds. - Memory-handling robustness and exhaustive cppcheck in CI (#84, #85, #82, #78). Tightened allocation/free paths in
src/common/common-stats.cand a few buffer sites that could leak or double-free under memory pressure.make lintnow runscppcheck --check-level=exhaustiveagainst a source-built cppcheck 2.20 (Ubuntu's 2.13 silently misses OOM checks), and CI fails on new findings — so future OOM-path regressions surface before merge instead of after.
4.12.2¶
Build hygiene release. No runtime changes.
- Clang x86_64 build support (#68).
make CC=clangnow compiles cleanly on Linux x86_64 —src/common/crc32.cpreviously relied on GCC-only__builtin_ia32_*names for SSE/PCLMUL ops; clang gets_mm_*intrinsic shims for the missing ones. The shim block is gated to__x86_64__/__i386__so Apple Silicon clang isn't affected. - Clang x86_64 CI job (#72). New
build-clangjob in the test matrix runsmake CC=clangso future regressions in the clang build path surface in CI instead of in third-party packaging.
4.12.1¶
Hotfix for log spam introduced by the 4.12.0 unique-IPs counter rework.
- Fix
WARNING: IP tracking table full for secret 0flooding docker logs (#71). The 4.12.0 fix for #70 made every secret populate a fixed-size 256-entry per-IP table; on a busy public proxy with nomax_ipsorrate_limitconfigured, the table fills as soon as ~256 distinct source IPs have connected and every subsequent unique client logs a warning. Plain secrets now bypass the precise table entirely and feedteleproxy_secret_unique_ipsfrom a per-secret Bloom filter — bounded memory, no overflow, ~0.5% FPR at 10K IPs. - Throttle the table-full warning to once per minute per slot for limit-bearing secrets where
max_ipslegitimately exceeds the table size. The message now also includes the secret label and a hint to raisemax_ipsor check the configuration. - The macro
SECRET_MAX_TRACKED_IPSis now overridable at build time (make EXTRA_CFLAGS="-DSECRET_MAX_TRACKED_IPS=N"), used by the newmake test-table-fullregression test.
4.12.0¶
Bug fixes for Docker deployments and per-secret metrics.
- Fix
teleproxy_secret_unique_ipsalways reporting 0 (#70). The counter only ever incremented for secrets that hadmax_ipsorrate_limitconfigured — a stale guard insideip_track_connect()that broke the metric for every plain secret. Removed the guard so the IP-tracking table populates for all secrets. Also clarified the metric type:teleproxy_secret_unique_ipsiscounter(cumulative count of distinct source IPs since startup), notgauge. - Clarified
teleproxy_secret_bytes_received_total/_sent_totalHELP text (#70). The labels are technically correct ("received from clients" = uploads, "sent to clients" = downloads) but easy to misread from a client perspective. The HELP lines now explicitly call out the direction. Note: these counters increment in direct mode only — relay-mode aggregation is a separate gap, tracked for a follow-up. - Fix Docker
SECRET=hex:label,hex:labelwriting the entire string as the TOML key (#67). The numbered-secret path (SECRET_LABEL_N) was already correct; the comma-separated path now mirrors it and writes a separatelabel = ...line. - New
EXTERNAL_PORTenv var (#66) for advertising a different port in the connection link than the internal listen port — needed when Docker maps-p 4443:443. Also added a matchingexternal_portTOML option, consumed by both the operator-printed connection URL and the/linkHTML page (QR codes).
4.11.0¶
SOCKS5 upstream support in the check command (#57) and a new Cloudflare Spectrum deployment guide (#55).
teleproxy checknow routes DC probes through the configured SOCKS5 proxy — new--socks5 URLCLI flag, also reads from thesocks5field in TOML config. Useful for environments where the proxy itself sits behind a SOCKS5 upstream.- Handle buffer allocation failures gracefully instead of crashing (#58). Under memory pressure,
rwm_allocand related functions could return NULL and trigger a segfault. Now they fail the connection cleanly. - Fix PROXY protocol metrics always reporting 0 in multi-worker mode (#53).
teleproxy_proxy_protocol_connections_totalandteleproxy_proxy_protocol_errors_totalwere not aggregated from worker processes — the master read its own zero copy. Both the text stats and Prometheus endpoints now report correct values. - New deployment guide for running behind Cloudflare Spectrum.
4.10.0¶
Graceful connection draining on secret removal (#45) and a brand-new signed RPM repository (#21).
- Zero-downtime secret rotation — removing a secret from
config.tomland sending SIGHUP no longer drops the in-flight connections that were authenticated under it. The slot enters a "draining" state: new connections matching that secret are rejected, but existing ones keep working until they close on their own ordrain_timeout_secs(default 300,0= infinite) elapses. Re-adding a draining secret revives the same slot — counters and IP tracking carry over. - New TOML option
drain_timeout_secs(reloadable). Pinned-SCLI secrets are immutable across SIGHUP and never drain. - New stats:
secret_<lbl>_draining,secret_<lbl>_drain_age_seconds,secret_<lbl>_rejected_draining,secret_<lbl>_drain_forced. Same fields exposed in Prometheus asteleproxy_secret_draining,teleproxy_secret_drain_age_seconds,teleproxy_secret_rejected_draining_total,teleproxy_secret_drain_forced_total. - Slot capacity expanded internally: 16 active secrets at a time as before, plus up to 16 additional draining slots.
- Fixes a latent bug where the per-secret connection counter could go negative if a TLS connection was closed between the TLS handshake and the obfs2 init.
- Signed dnf repository at https://teleproxy.github.io/repo/ serving RHEL 9, RHEL 10, AlmaLinux, Rocky Linux, and Fedora 41/42 on x86_64 and aarch64. Install on any of these distros with
dnf install https://teleproxy.github.io/repo/teleproxy-release-latest.noarch.rpm && dnf install teleproxy && systemctl enable --now teleproxy. Packages are signed with an RSA 4096 / SHA-512 key (RHEL 9 rpm-sequoia compatible). The first install generates a random secret in/etc/teleproxy/config.toml; subsequentdnf upgraderuns swap the binary and never touch user-edited config;dnf removeleaves the config file in place. Built automatically on each tag from the existing static linux binaries via nfpm, driven byrepository_dispatchfrom the release workflow into the new teleproxy/repo repository.
4.9.0¶
PROXY protocol v1/v2 listener support (#50) and per-IP top-N Prometheus metrics (#46).
- PROXY protocol — accept HAProxy PROXY protocol v1 (text) and v2 (binary) headers on client listeners. Required when running behind a load balancer (HAProxy, nginx, AWS NLB) that injects client IP. Enable with
--proxy-protocol(CLI),proxy_protocol = true(TOML), orPROXY_PROTOCOL=true(Docker). - Auto-detects v1 and v2 headers, extracts real client IP, re-checks IP ACLs against the real address.
- v2 LOCAL command accepted for load balancer health check probes.
- New Prometheus metrics:
teleproxy_proxy_protocol_connections_total,teleproxy_proxy_protocol_errors_total. - Per-IP top-N metrics — opt-in
top_ips_per_secret = N(TOML) orTOP_IPS_PER_SECRET=N(Docker) exposes the top-N heaviest client IPs per secret in/metrics. Three new families:teleproxy_secret_ip_connections,teleproxy_secret_ip_bytes_received_total,teleproxy_secret_ip_bytes_sent_total. Sorted by total bytes; cap at 32 per secret to keep Prometheus cardinality bounded. Default 0 (disabled, zero overhead). Useful for diagnosing the "proxy works for 5 minutes then stops" complaint pattern alongside the Grafana dashboard. - Fix auto-generated secret not written to TOML config —
start.shnow correctly stores the generated secret in the TOML config. - Documentation: complete SEO overhaul with per-page meta descriptions, OpenGraph tags, JSON-LD structured data, and robots.txt.
- Translations: Russian documentation now at 100% coverage, Farsi and Vietnamese expanded to 38%.
- TON wallet added as a donation option alongside Tribute.
4.8.0¶
DC health probes (#47).
- DC latency probes - periodic TCP handshake measurement to all 5 Telegram DCs, exposed as Prometheus histograms (
teleproxy_dc_latency_seconds), failure counters, and last-latency gauges. Helps operators diagnose slow downloads and pick optimal DC routing. - Disabled by default. Enable with
--dc-probe-interval 30(CLI),dc_probe_interval = 30(TOML), orDC_PROBE_INTERVAL=30(Docker env). - Probes run in the master process only. Completion is tracked via non-blocking poll to preserve sub-millisecond accuracy.
- Text stats endpoint includes per-DC latency, average, count, and failure fields.
4.7.0¶
Per-secret quotas, unique-IP limits, and expiration (#26).
- Data quota — cap total bytes transferred per secret; active connections are closed and new ones rejected when exhausted. Configurable in bytes or human-readable sizes (
quota = "10G") - Unique IP limit — cap how many distinct client IPs can use a secret simultaneously (
max_ips = 5). Additional connections from an already-connected IP are always allowed - Secret expiration — auto-disable a secret after a timestamp (
expires = 2025-12-31T23:59:59Z). Existing connections continue; only new ones are rejected - Per-reason rejection counters in Prometheus and plain-text stats (
rejected_quota,rejected_ips,rejected_expired) - Docker env vars:
SECRET_QUOTA_N,SECRET_MAX_IPS_N,SECRET_EXPIRES_N - SOCKS5 upstream proxy support (#22)
- One-click cloud deploy page
- Documentation: install/upgrade instructions, SOCKS5 docs, Observatory link
4.6.0¶
DPI resistance and operational improvements.
- ServerHello size variation widened from ±1 to ±32 bytes, mimicking the natural variation in certificate chain and session ticket sizes seen from real TLS servers
- ServerHello fragmentation: ServerHello and CCS+AppData are now sent as separate TCP segments, defeating DPI that pattern-matches the full handshake response in a single packet
- Docker healthcheck respects custom
STATS_PORT— previously hardcoded to 8888, now uses${STATS_PORT:-8888}(#38) install.shsupports multiple secrets via comma-separatedSECRETor numberedSECRET_Nvariables/linkendpoint serves connection links as HTML pages with scannable QR codes
New documentation: DPI Resistance — covers server-side mitigations, recommended setup, and client-side bypass tools.
Client-side detection
The primary detection vector for MTProxy fake-TLS in Russia is the Telegram client's TLS fingerprint, which cannot be fixed server-side. Telegram Desktop fixed several fingerprint artifacts; keep clients updated. For affected networks, client-side bypass tools like zapret and GoodbyeDPI can help.
4.5.0¶
QR codes for connection links.
teleproxy linksubcommand prints a proxy URL and renders a scannable QR code in the terminal using UTF-8 half-block characters- Docker
start.shandinstall.shnow display QR codes automatically at startup — point a phone camera at the screen to connect - Vendored nayuki/QR-Code-generator (MIT) for zero-dependency QR rendering on any platform
- E2E tests decode the rendered QR output with pyzbar and verify it matches the expected URL
- Documentation: new "Connection Links" page (en + ru)
4.4.0¶
teleproxy checkdiagnostic subcommand — validates configuration and tests connectivity before accepting clients. Checks DC reachability, NTP clock drift, fake-TLS domain probe, and SNI/DNS mismatch. Exit 0/1/2 for pass/fail/bad-args.
4.3.0¶
Direct mode connection resilience.
- IPv6 auto-detection: probe at startup, enable without
-6if reachable - Multiple addresses per DC with synchronous failover on connect failure
- Connection retry with exponential backoff (200ms–800ms, 3 attempts)
--dc-override dc_id:host:portto add or replace DC addresses (repeatable). Docker:DC_OVERRIDE=2:1.2.3.4:443,2:5.6.7.8:443- New stat:
direct_dc_retries/teleproxy_direct_dc_retries_total
4.2.1¶
- Fix aarch64 build: remove unused x86-only
sys/io.hinclude - Add native ARM64 glibc build to CI (catches platform-specific issues masked by Alpine/musl)
4.2.0¶
--stats-allow-net CIDRflag to extend stats endpoint access beyond RFC1918 ranges (repeatable). Docker:STATS_ALLOW_NET=100.64.0.0/10,fd00::/8
4.1.0¶
MTProto transport protocol compliance improvements.
- Detect and log transport error codes (-404, -429, etc.) from DCs in direct mode
- Detect transport error codes in medium mode client parse path
- Track quick ACK packets with
teleproxy_quickack_packets_totalcounter - Track transport errors with
teleproxy_transport_errors_totalcounter
4.0.0¶
Rebrand to Teleproxy. Binary renamed from mtproto-proxy to teleproxy.
- Binary name:
teleproxy(wasmtproto-proxy) - Prometheus metrics prefix:
teleproxy_(wasmtproxy_) - Docker user/paths:
/opt/teleproxy/(was/opt/mtproxy/) - Environment variables:
TELEPROXY_*(oldMTPROXY_*still accepted with deprecation warning) - Docker image includes backward-compat symlink
mtproto-proxy -> teleproxy - CLI flags and behavior unchanged