github valnesfjord/tg-ws-proxy-rs v2.0.0

4 hours ago

tg-ws-proxy-rs v2.0.0

Brings the Cloudflare paths to parity with Flowseal/tg-ws-proxy v1.9.1 and fixes what that gap was breaking for users.

All CLI flags and TG_* environment variables are unchanged — upgrading is a drop-in binary swap. The major version is about the library API only (see the bottom).

Fixed

Uploads over a Cloudflare Worker no longer break on files above ~1 MB. A Worker is a raw TCP tunnel, so MTProto packet boundaries carry no meaning at its far end — but Cloudflare drops WebSocket messages larger than 1 MiB, which is exactly what a media upload's packets exceed. The stream is now chunked by client reads on that path instead of packet-aligned. Same fix upstream shipped in v1.9.1 (issues #1161, #1155). The --cf-domain path still packet-aligns — Telegram's own /apiws endpoint requires it.

--cf-priority now covers --cf-worker-domain. It only ever applied to --cf-domain, so a Worker-only setup sat out the flag entirely and waited out the full direct-WebSocket timeout on every single connection before reaching the one tier that worked. That is what left Telegram stuck on "Connecting…" (#93).

A blocked DC address is no longer re-probed on every connection. A --dc-ip address whose TCP connect times out is stepped over for --ip-fail-cooldown seconds (default 1 hour) when a Cloudflare or upstream-proxy tier is configured, instead of costing every later client another connect timeout. Only a timeout counts — a refused or redirected connection does not — and only at the full connect timeout, not the short probe a DC already in cooldown gets.

Stepped over, not written off: if every fallback tier is failing too, the address is re-probed rather than parking clients on raw TCP for the rest of the window, and the first successful direct connect clears the cooldown.

Domain fronting now triggers on the failure it was built for. A stalled TCP connect and a stalled TLS/WebSocket handshake used to arrive as the same opaque error, so neither fallback fired on the most common real-world timeout. They are now told apart: a stalled handshake means the address answered and the handshake went nowhere — the SNI-blocking signature fronting works around — while a TCP connect that never completed means the address is unreachable, and a different SNI on a connection that cannot be opened changes nothing.

--check actually tests a Cloudflare Worker now. Cloudflare returns 101 from the Worker script before — and regardless of whether — its own connect() to the Telegram DC succeeds, so the upgrade alone proved nothing: a Worker that could not reach Telegram passed the check while every real client through it died instantly. The probe now pushes a real MTProto init through the tunnel and catches one that closes immediately. Expect roughly 3 extra seconds per Worker domain.

Worker tunnels open onto the DC's built-in address rather than a local --dc-ip override. The Worker dials from Cloudflare's network, where this host's routing preference means nothing, and a stale override just sent the tunnel somewhere dead.

Faster

Connection pooling for both Cloudflare tiers (Worker tunnels and --cf-domain). A client reaching Telegram through Cloudflare pays two handshakes — to the Cloudflare edge, then Cloudflare's own connection onward — before its first byte moves, and Telegram opens a fresh connection for every media transfer, so on a blocked network that cost lands on every download.

At most one idle connection per (tier, DC, media), and it is only ever opened off a connection that just worked, so a dead Worker is not re-dialled in the background on every client. --pool-size 0 turns pooling off. With --cf-balance, the spare is opened through the next domain in the rotation instead of pinning itself to the one that answered.

The pool stops pre-connecting into a dead address. An address that is currently timing out no longer gets --pool-size doomed background connects per client connection.

Lower per-connection overhead: the configuration is shared rather than deep-copied on every accept (with --default-domains that was dozens of string copies per client), Worker domains are normalized once at startup instead of on every read from the routing path, and the tunnel path builds no intermediate frame list per read.

New

  • --ip-fail-cooldown <SECS> / TG_IP_FAIL_COOLDOWN — how long to step over a --dc-ip address whose TCP connect timed out. Default 3600, matching upstream's IP_FAIL_COOLDOWN.

Breaking (library API only)

Binary and CLI users are unaffected. For anyone using the crate as a library:

  • handle_client / handle_client_with_runtime take Arc<Config> instead of Config.
  • OutboundConnector::connect returns OutboundError instead of String; the error now carries whether the failure was a timeout.
  • connect_ws_for_dc_with_outbound returns a WsAttempt struct instead of a three-element tuple.
  • connect_cf_ws_for_dc / connect_cf_ws_for_dc_with_outbound additionally return the record that answered.
  • WsConnectResult gains a ConnectTimedOut variant.
  • Config::cf_worker_domains returns &[String] and Config::cf_worker_domain returns Option<&str>; both now expect Config::with_defaults() to have run, which Config::from_args() does.

Known limitations

Memory use is higher than it should be — around 25 MB RSS with two active clients. It is being tracked separately and does not affect stability.

Still to come for full upstream parity: background pool rotation and refill backoff (upstream v1.9.0), honouring --buf-kb for socket buffers, domain randomization without --cf-balance, and test-DC support.

Verified

187 tests, clean clippy, plus a regression test that drives 3 MiB through the Worker bridge and asserts no frame exceeds one client read — with the old behaviour the same payload goes out as a single 3 MiB message.

Live-tested against real Telegram clients over --cf-domain + --default-domains + --cf-balance: 251 sessions, 100 pool hits, zero errors, with sustained bidirectional media traffic.

Thanks to @kpbicka and @Dum4G for the reports and testing.

Don't miss a new tg-ws-proxy-rs release

NewReleases is sending notifications on new releases.