# MTproxy-reanimation

MTproxy-reanimation ("реаниматор") fixes one specific failure: a Telegram MTProto proxy whose clients cannot complete the first TCP connection. The proxy itself works; the handshake is what gets killed. The tool is a bash menu called `mtpr` that installs nftables rules to survive that, plus a small amount of safe tuning for Telemt and MTProxyMax. It does not replace either — it sits beside them and manages kernel-level rules and a few config values.

Installation locates the proxy on its own: MTProxyMax, a Docker container, a systemd unit, or a bare local process. It reads the Telemt config to find the API port (`[server.api]`, default 9091), backs the config up before touching it, and offers to restore from that backup on uninstall. Telemt Panel configs are explicitly left alone.

## Picking the netfilter hook

The first non-obvious thing it does is choose the hook by deployment shape. Telemt on the host or on host networking gets rules in `input`. Telemt in a Docker bridge network gets `forward`, because the packets are being routed to the container rather than delivered locally. For the bridge case there are two variants: a simple mode that matches the port only, and a precise Docker mode that matches the container's internal IP and runs a watcher service (`mtpr-bridge-watch.service`) to follow it when the address changes. The README's advice is to try precise first.

## SYN limiter: Classic and Smart

The original mechanism is a per-source inbound SYN rate limit. Classic mode is one meter for everyone:

```
tcp dport <PORT>
tcp flags & (syn | ack) == syn
meter { ip saddr timeout 60s limit rate over 1/second burst 1 packets }
counter drop
```

Limiting *inbound* connection attempts to fix a connectivity problem reads backwards until you see what it is for. A Telegram client under interference retries the handshake aggressively across several candidate paths; throttling the flood makes it settle on a working one faster instead of thrashing. The Classic mode's weakness is that it uses `drop`, so a rate-limited client sits in a retransmit timeout and connection setup takes 10–20 seconds.

Smart By-MEKO, the recommended mode, splits clients into two branches and changes the rejection. iOS clients are identified by a **TCP SYN fingerprint** — raw offset matches into the TCP header, matching option layout and window values rather than heuristics:

```
@th,108,20 0x2ffff  @th,160,16 0x204  @th,192,16 0x103
@th,224,24 0x10108  @th,320,32 0x4020000
```

iOS gets a loose meter (rate 15/second, burst 30) and a TCP reset when it exceeds it; everything else gets a strict one (rate 54/minute, burst 1) and, by default, `reject with icmp type host-unreachable`. Both branches share a single port. The point of ICMP over `drop` is that Telegram treats an explicit rejection as a fast failure and moves to another connection immediately, which the README credits with cutting connect time to 3–8 seconds and, specifically, removing a long stall when sending media from Android and Desktop clients. Either branch's limit can be disabled outright. An older fingerprinting method — `ip ttl < 65 AND meta length 64` — is kept selectable for compatibility.

The fingerprint approach came from [MTPROTO-FIX-By-MEKO](https://github.com/Mekotofeuka/MTPR-FIX-By-MEKO), which the README credits for both the iOS/Android split and the fast-reject idea.

## Zapret2 mode: the server does the desync

Version 1.2.0 (July 2026) added a different mechanism entirely, built on [zapret2](https://github.com/bol-van/zapret2)'s `nfqws2` packet mangler with Lua scripts. It replaces the SYN limiter rather than supplementing it — turning it on disables the limiter automatically.

The sequence, all executed on the server against its own outbound packets:

1. The SYN+ACK goes out with `window = 1400`, so the client is forced to fragment its first data packet instead of sending the ClientHello whole.
2. Empty ACKs from the server carry `window = 10` until the client has actually sent payload, keeping the pressure on.
3. The client's first data packet — the Fake-TLS ClientHello — is cut into three pieces. The first goes out normally, the third goes out normally at its offset, and the middle one goes out with a **deliberately corrupt checksum**.
4. A middlebox sees an incomplete, out-of-order ClientHello it cannot parse, and the corrupt segment is dropped before it reassembles.
5. The client retransmits the middle piece, the connection completes, and everything after that passes untouched — released via `fwmark` plus conntrack marks and an `instance_cutoff` in the Lua script.

This is the mirror image of [[tls-desync-fake-clienthello]]. There, the *client* confuses the DPI by sending a decoy ClientHello with a low TTL before the real one; here the *server* confuses it by making sure no complete ClientHello ever crosses the middlebox in one readable piece. Same target — the SNI-inspecting path in something like [[tspu]] — opposite end of the wire. It shares the split/disorder/badsum vocabulary with the wider zapret family, and unlike [[bypassing-dpi-with-ebpf|gecit]]'s eBPF approach it needs nothing installed on the client.

iOS clients are exempted: they are fingerprinted at SYN, marked with `fwmark = 0x40000`, and sent through `rawsend_dissect_segmented` without window manipulation, because the window games degrade their connections. Earlier versions just passed them (`VERDICT_PASS`), which let the packets loop back into the netfilter queue.

The nftables table is four chains, arranged so that already-processed packets skip requeueing:

```
table ip MTProto {
    chain predefrag   # output priority -401: fwmark accept + notrack
    chain output      # route mangle: ct mark set for marked packets
    chain postrouting # srcnat+1: ct mark accept + queue
    chain prerouting  # mangle: ct mark accept + queue
}
```

### The wscale constraint

The bypass only works if the advertised receive window is genuinely small, and TCP window scaling stands in the way. The effective window is `win_ACK × 2^wscale`, and the kernel picks `wscale` from the receive buffer size, so a server tuned for throughput cannot advertise a small enough window at all:

| wscale | 2^wscale | usable win ACK | effective window |
|---|---|---|---|
| 7 | 128 | 10 | 1280 bytes |
| 9 | 512 | 2 | 1024 bytes |
| 11 | 2048 | — | impossible |

At `wscale ≥ 11` (a 64 MB+ buffer) there is no integer that lands under 1400, and the installer prints instructions for shrinking `net.core.rmem_max` instead. This check runs at install and again under the diagnostics menu item, and it has to be redone whenever kernel buffer settings change. It is a nice example of a circumvention technique whose viability is decided by an unrelated sysctl.

## Tuning and the rest of the menu

Beyond the netfilter work it manages three Telemt timeouts — `tg_connect` 30, `client_handshake` 90, `client_keepalive` 120 — and the `client_mss` / `client_mss_bulk` values, which the README recommends disabling under both Smart and Zapret2 modes since they solve the same problem worse. It pulls per-user `tg://proxy?...` links from the Telemt API through `jq`, filtering out IPv6 entries and honouring `public_host`/`public_port`. The two older iOS workarounds (TCP keepalive sysctls; a separate port 4443 with MSS=92 and a transparent redirect) have been demoted to a "legacy settings" submenu, obsoleted by both current modes.

One piece of advice in the README is worth extracting because it has nothing to do with the tool: the domain used for Fake-TLS must support post-quantum hybrid key exchange (`X25519MLKEM768`), or iOS clients will not connect at all. No amount of nftables tuning fixes that.

## Caveats

The repository was created on 2026-06-10 and is one author's bash, at 294 stars and 5 forks. It installs via `curl | sudo bash`, writes systemd units, and edits kernel-level firewall rules on a live server — read the script first. Everything above is the README's account, in Russian, of behaviour on the author's own deployments; there are no independent measurements of the 3–8 second figure or the media-sending stall. Docker bridge mode is untested against zapret2 per the README's own compatibility note. And the moving target is upstream: Telemt 3.4.18+ added features that overlap what the reanimator does, and the README says to pick one or the other rather than run both.

Tagged `watchlist` — see [[toolbox/watchlist]]. Censorship-circumvention tooling has a short half-life, and this one is pinned to a single proxy implementation that is absorbing its functionality.

For the client side of the same problem, see [[xray-tutorial]] and [[happ-proxy]]; for routing across the resulting proxies, [[mihomo]] and [[wirez]].

MIT, ~294★ as of 2026-07-29.
