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

4 hours ago

The upstream tier order can now be pinned separately for media and non-media
connections.

Why

Every client handshake already says whether it wants a media DC (negative DC
index) — where Telegram clients put photo/video/file transfers — or not. The
ladder was the same for both classes anyway, so there was no way to say
"control traffic direct over TCP from this network's residential IP, media
through Cloudflare" — which matters when keeping the control channel on the
residential IP is what keeps the account off the anti-spam radar (bulk
transfers can then ride any tunnel), or when only one class of upstream works
on your network.

Added

  • --pinned-upstream <TIERS> / TG_PINNED_UPSTREAM — pin the tier order.
    Applies to media connections too: media must not quietly slip back to the
    default ladder and undo the egress decision the operator made with the
    single flag.
  • --pinned-media-upstream <TIERS> / TG_PINNED_MEDIA_UPSTREAM — override
    the pin for media connections only.

TIERS is a comma-separated list of ws, cfworker, cfproxy, mtproto,
tcp, tried in the order given. Examples:

# Media transfers over raw TCP only; everything else keeps the default ladder
tg-ws-proxy --pinned-media-upstream tcp

# Control traffic pinned to upstream MTProto proxies, media to the CF proxy
tg-ws-proxy --pinned-upstream mtproto --pinned-media-upstream cfproxy

# Both classes over Tor via a SOCKS outbound proxy — the pins only pick
# tiers; the outbound proxy applies to every tier alike
tg-ws-proxy --outbound-proxy socks5h://127.0.0.1:9050 \
            --pinned-upstream tcp --pinned-media-upstream ws

Semantics:

  • The listed order is the fallback chain: a failing tier hands over to the
    next one listed, each with the same pool, cooldown and fronting behavior it
    has in the default ladder. --cf-priority is ignored for a pinned class;
    unpinned classes keep the default order.
  • A pin naming cfworker, cfproxy or mtproto with nothing configured
    behind it refuses to start, instead of dropping that class's connections
    one by one at runtime. ws is judged per connection — DCs without a
    --dc-ip target skip it with a log line.
  • When every listed tier fails, the connection is dropped: tiers left out of
    the pin are not quietly re-added as fallbacks. That is deliberate — a pin
    exists to keep a class on a particular path. List more tiers (or tcp
    last) if you want fallback breadth instead.
  • Per-tier failure cooldowns stay a "prefer to step over" hint, never a gate
    on the last tier that can connect: the Worker and upstream-MTProto rungs
    dial through their cooldown there, mirroring the default ladder whose
    terminal TCP rung always attempts. Without this, a single-tier pin would
    turn one transient failure into a cooldown-length outage — nothing else
    would dial, so nothing could clear the cooldown early.
  • tcp is terminal (the raw-TCP connect happens in the bridge), so tiers
    listed after it are never reached — startup warns about such a pin.
  • Pool warm-up is skipped for a class pinned without ws.

The split is per connection, not per packet: the proxy cannot see inside the
MTProto session, so "media" means "a connection to a media DC" — which is
where Telegram clients put bulk transfers anyway.

Changed

  • --cf-priority / TG_CF_PRIORITY are no longer documented: the pin covers
    the same need (cfworker,cfproxy,ws,mtproto,tcp). The flag still parses and
    routes identically — internally it expands into that pin, minus any tier not
    configured, so it stays a no-op where it always was — and is kept for
    already-deployed LuCI UCI options and systemd units, so nothing breaks on
    upgrade. The expansion always appends ws and tcp unconditionally, so it
    terminates in tcp for every configuration: a --cf-priority deployment
    keeps its old fall-through to raw TCP rather than inheriting the pin's
    drop-on-exhaustion, and the same goes for the cooldown skip — tcp has no
    cooldown, so the last rung of an expanded pin always attempts.

Cost

None by default: with neither flag set the routing code is byte-for-byte the
previous ladder. Pinned classes give up the automatic fallback breadth they
did not ask for — that is the feature, but it is also a foot-gun if you pin a
class to a single tier that later stops working.

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

NewReleases is sending notifications on new releases.