# telemt 3.4.x: чтение логов, TLS-фингерпринты и детект блокировок ТСПУ

> [!info] О чём заметка
> С версии **3.4.x** telemt пишет в логи **TLS-фингерпринт** (JA4/JA3) каждого подключающегося клиента. Это даёт редкую возможность увидеть **снаружи прокси**, по какому именно признаку ТСПУ режет соединения. Здесь — как читать эти логи, что значит ошибка `expected_64_got_0`, и разбор реального наблюдения: DPI начал блокировать **конкретную новую версию клиента Telegram** по её JA4.

> [!warning] Статус данных
> Ниже — **разбор логов одного сервера** (telemt 3.4.15) + сопоставление JA3 с публичными базами. Часть выводов (особенно «какой клиент это сломал») — **гипотезы по наблюдениям**, а не подтверждённая спецификация ТСПУ. Конкретные хеши и подсети — снимок на момент анализа; у вас будут другие. Читайте как метод, а не как готовые константы.

---

## Что значит `expected_64_got_0`

Это ключевая ошибка в логах. Чтобы понять её, вспомним порядок FakeTLS-рукопожатия:

1. Клиент → сервер: **TLS `ClientHello`** (здесь telemt и снимает фингерпринт).
2. Сервер → клиент: `ServerHello` + `ChangeCipherSpec` + `ApplicationData`.
3. Клиент → сервер: `ApplicationData`, внутри которого **64-байтный обфусцированный MTProto-хендшейк**.

telemt ждёт на шаге 3 ровно **64 байта**. Запись:

```
expected 64 got 0
```

означает: **ClientHello пришёл** (фингерпринт снят), но потом соединение **закрылось, не прислав ни байта** обфусцированного заголовка. Клиент так себя не ведёт — он всегда досылает 64 байта. А вот **DPI ведёт себя именно так**: видит ClientHello, опознаёт его как Telegram по фингерпринту и **рвёт/душит соединение** до того, как пойдут полезные данные.

> [!important] Вывод
> Массовый `expected_64_got_0` с одной подсети/оператора = **активная блокировка по TLS-фингерпринту**, а не сетевой сбой. `got 0` (а не `got N`) — признак именно обрыва после ClientHello.

---

## Как читать JA4-фингерпринт в логах

telemt пишет JA4 вида `t13d2014h2_<cipher_hash>_<extension_hash>`. Расшифровка первой части (`a`-сегмент) — по [[VLESS/dpi-tls-june-2026|той же схеме JA4 (разбор uTLS)]]:

| Поле | `t13d2014h2` | Значение |
|---|---|---|
| `t` | TLS-over-**TCP** (`q` = QUIC) | транспорт |
| `13` | TLS **1.3** | версия (из `supported_versions`) |
| `d` | **domain** — SNI есть (`i` = по IP) | наличие SNI |
| `20` | **20** cipher suites | число шифров |
| `14` | **14** extensions | число расширений |
| `h2` | ALPN = `h2` (HTTP/2) | первый/последний символ первого ALPN |

Дальше идут два хеша: `_<cipher_hash>_<extension_hash>` — усечённые SHA256 от **отсортированных** наборов шифров и расширений. Именно по ним отличают версии клиента при одинаковом `a`-сегменте.

---

## Корень проблемы: фингерпринт клиента «протух» (tdesktop #30733)

Самое важное — из официального issue [telegramdesktop/tdesktop#30733](https://github.com/telegramdesktop/tdesktop/issues/30733) (тесты блокировки начались **22 мая в Сибири**):

> Telegram Desktop в FakeTLS **мимикрирует под браузер**, и сейчас шлёт фингерпринт `t13d1516h2_8daaf6152771_d8a2da3f94cd` — это **Chrome 134 на macOS**. Но реальный Chrome уже 148 (на Win — `t13d1514h2_8daaf6152771_827b515c4f52`). Пресет **заморожен на старой версии**, и эта несвежесть сама стала маркером.

Что тут видно при сравнении двух JA4:

| | Telegram Desktop (мимикрия) | Реальный Chrome 148 (Win) |
|---|---|---|
| JA4 | `t13d1516h2_8daaf6152771_d8a2da3f94cd` | `t13d1514h2_8daaf6152771_827b515c4f52` |
| Шифры (cipher hash) | `8daaf6152771` | `8daaf6152771` ← **совпадает** |
| Расширений | **16** | **14** |
| Extension hash | `d8a2da3f94cd` | `827b515c4f52` |

Cipher-хеш **тот же** (Telegram честно копирует набор шифров Chrome), но **набор расширений разъехался** — Telegram застрял на раскладке Chrome 134, а живой Chrome ушёл вперёд. DPI ловит именно это расхождение: «почерк Chrome, но **такого Chrome уже нет в природе**».

> [!important] Ключевой вывод
> Это ровно «**несвежесть пресета**» из [[VLESS/dpi-tls-june-2026|сибирской заметки]] (там это про uTLS в REALITY, здесь — про встроенный FakeTLS-пресет Telegram). Лечится это **только в самом клиенте Telegram** (issue просит ротацию/обновление фингерпринтов). **Оператор прокси фингерпринт не меняет** — отсюда и весь набор костылей ниже (дробление, desync, SYN-ACK), которые прячут/ломают опознание ClientHello, а не правят его.

---

## Разбор наблюдения (telemt 3.4.15, июнь 2026)

> [!warning] Точность хешей
> Ниже — обработка логов одного сервера (частично нейросетью). Конкретные cipher/extension-хеши тут могут быть **неточными** — авторитетные значения берите из [issue #30733](https://github.com/telegramdesktop/tdesktop/issues/30733) выше. Доверять стоит **структуре** наблюдения (два семейства, новый фингерпринт чаще падает), а не отдельным хешам.

### Два семейства по cipher-хешу

| Семейство | Cipher hash | Шифров | Кто это |
|---|---|---|---|
| **Мобильные** (Android/iOS) | `a09f3c6…` | 20 | BoringSSL — стек мобильного Telegram |
| **Desktop** | `f57a46b…` | 13 | Telegram Desktop |

Extension-хеш `7f0f34a4126d` **совпадает** у мобильных и десктопа → набор расширений у них одинаковый, различаются только шифры (мобильный BoringSSL тащит больше cipher suites).

### Сопоставление JA3 с публичными базами

JA3 этих клиентов уже лежат в открытых базах (ja3er.com, sslbl.abuse.ch) — то есть они **давно известны** и ТСПУ их видел:

| JA3 | Клиент |
|---|---|
| `ecdf4f49dd59effc439639da29186671` | Telegram **Android** |
| `8527da8b8a640065e72ec6b6f99764f3` | Telegram **Desktop** |

### Новый фингерпринт — и именно он ломается

`t13d2014h2` с extension-хешем **`e42f34c56612`** — мобильный клиент, у которого **изменился набор расширений** (отсюда другой ext-хеш и счётчик `…14` расширений). Похоже на **свежее обновление приложения**, добавившее один extension.

И вот суть:

> **Именно `t13d2014h2` (`e42f34c56612`) чаще всего падает в `expected_64_got_0`.** На МТС подсеть `95.24.149.x` почти целиком состоит из этого фингерпринта.

Интерпретация: ТСПУ **научился резать конкретно новую версию клиента** по её свежему JA4. Старые фингерпринты (которые в публичных базах) проходят, а только что появившийся — рубится. Это укладывается в логику «волны проблем после обновления Telegram»: сломалось не у всех, а у тех, кто обновил приложение до версии с новым extension.

> [!note] Осторожно с причинностью
> Альтернативное объяснение: новый клиент мог сам поменять тайминг/поведение хендшейка, и `got 0` — побочка, а не таргетированный блок по JA4. Но концентрация одного фингерпринта в одной подсети одного оператора склоняет к версии «таргетированный детект». Проверяется сравнением: режется ли тот же JA4 у других операторов и на других подсетях.

---

## Лимит SYN-ACK — помогает или нет

Гуляет приём — дропать **исходящие SYN-ACK** сверх 1/сек правилом nft. Его часто называют то «вредным», то «волшебным» — на деле всё посередине: он **иногда реально помогает** (в т.ч. на telemt), но с понятной ценой. Разберём честно.

> [!example] На пальцах: что такое SYN-ACK и почему его дроп сбивает DPI
> Установка TCP-соединения — это как звонок:
> 1. Клиент: **«SYN»** — «Алло, можем говорить?»
> 2. Сервер: **«SYN-ACK»** — «Да, говори». ← вот его и дропаем
> 3. Клиент: **«ACK»** — «Ок». И только теперь шлёт **ClientHello** — свою «визитку» (тот самый почерк, который ловит DPI).
>
> Если сервер иногда **роняет своё «Да, говори»**, происходит две полезные вещи:
> - **DPI теряет нить.** Цензор-«подслушка» строит запись о разговоре по этому рукопожатию. Когда «Да, говори» приходит с задержкой и повтором, запись у DPI получается кривая — и он **не привязывает** к ней визитку клиента, то есть не опознаёт её.
> - **Нет «залпа».** Пока «Да, говори» не дошло, клиент **молчит** и визитку не отправляет. Значит визитки выходят не пачкой, а по одной в секунду — и поведенческий триггер «много коннектов разом» не срабатывает.
>
> Минус — звонок дольше устанавливается (надо переспросить через секунду). Для Telegram это разовая задержка на старте, потом соединение живёт долго — поэтому терпимо.

### Что вообще делает правило

```nft
add table inet filter
add chain inet filter output { type filter hook output priority 0; }
add rule inet filter output tcp flags & (syn|ack) == syn|ack \
  tcp sport 45443 limit rate over 1/second burst 1 packets drop
```

`45443` — порт, на котором слушает telemt. Правило дропает **второй пакет TCP-рукопожатия** (SYN-ACK, сервер→клиент), когда их больше 1/сек.

### Почему это может ломать блокировку

Два механизма, оба правдоподобны:

1. **Десинхронизация stateful DPI.** Дроп своего SYN-ACK заставляет ядро **переслать его с задержкой**. Рукопожатие завершается «криво» и позже — а stateful-трекер ТСПУ, который строит запись о потоке по хендшейку, на аномальном/переотправленном SYN-ACK может **не привязать** последующий ClientHello к потоку и не проинспектировать его. По духу это родственно nfqws-desync, только на уровне SYN-ACK.
2. **Слом «залпа соединений».** Пока SYN-ACK не дошёл, клиент **не шлёт ClientHello** (TCP ещё не установлен). Значит частота ClientHello троттлится до 1/сек → рушится поведенческий Сигнал 3 ([[VLESS/dpi-tls-june-2026|сибирская схема]]: «>3 коннектов с интервалом <100мс»).

### Цена и оговорки

- **Медленная установка соединений.** Каждый коннект сверх лимита ждёт ретрансмит SYN-ACK (~1с+). Для Telegram с **долгоживущими** соединениями это разовая задержка на старте — терпимо. Для high-churn — больно.
- **Глобальный вариант калечит всех юзеров разом** — один бюджет 1/сек на весь порт. На многопользовательском прокси это плохо → нужен **per-port** вариант (ниже).
- **Не лечит корень** (протухший фингерпринт из #30733) — это обходной костыль, может перестать работать при адаптации ТСПУ.
- **Сервер чуть аномален** статистически, но «реально проходит» важнее «выглядит идеально».

> [!tip] Вердикт
> **Тестируйте на своём маршруте, и сразу per-port вариант.** Это не замена дроблению/desync/`mask`, а дополнение к ним. Заработало — оставляйте, но мониторьте логи (`expected_64_got_0`), чтобы поймать момент, когда ТСПУ адаптируется и приём перестанет действовать.

### Per-port вариант — правильный (бюджет 1/сек на каждый порт)

Проблему «один лимит на всех» решает раздача юзерам **разных портов** из диапазона + лимит, считаемый **по порту**:

```nft
table ip telemt_limit {
  set synack_ports {
    type inet_service
    size 65535
    flags dynamic,timeout
    timeout 5s
  }
  chain prerouting {
    type nat hook prerouting priority dstnat; policy accept;
    tcp dport 4000-5000 redirect to :443          # все порты 4000-5000 → telemt:443
  }
  chain postrouting {
    type filter hook postrouting priority srcnat + 1; policy accept;
    tcp flags & (syn|ack) == syn|ack tcp sport 4000-5000 \
      update @synack_ports { tcp sport limit rate over 1/second burst 1 packets } drop
  }
}
```

Командами:

```bash
nft add table ip telemt_limit
nft add set ip telemt_limit synack_ports { type inet_service \; flags dynamic, timeout \; timeout 5s \; }
nft add chain ip telemt_limit prerouting { type nat hook prerouting priority dstnat\; }
nft add rule ip telemt_limit prerouting tcp dport 4000-5000 redirect to :443
nft add chain ip telemt_limit postrouting { type filter hook postrouting priority srcnat + 1\; }
nft add rule ip telemt_limit postrouting tcp flags \& \(syn \| ack\) == syn \| ack tcp sport 4000-5000 \
  update @synack_ports { tcp sport limit rate over 1/second burst 1 packets } drop
```

**Простыми словами:** вместо одного порта 443 ты даёшь каждому пользователю **свой** порт из диапазона 4000-5000 (в ссылке). Все они внутри ведут на тот же telemt, но лимит «1 соединение в секунду» считается **отдельно для каждого порта**. Поэтому активность одного пользователя больше не мешает остальным — у каждого свой бюджет.

Как это работает технически:

- **`prerouting` (DNAT):** клиент может коннектиться на любой порт `4000-5000`, всё редиректится («заворачивается») на telemt:443. Раздаёшь каждому юзеру/ссылке **свой порт** из диапазона.
- **`postrouting` (priority `srcnat+1`):** правило срабатывает **после** un-NAT, когда sport SYN-ACK уже переписан обратно в клиентский порт (4000-5000). `update @synack_ports { tcp sport limit rate ... }` заводит **отдельный счётчик 1/сек на каждый порт** (элементы set живут 5с).
- **Итог:** каждый раздаваемый порт лимитируется **независимо** → один юзер не давит других. Это снимает главное возражение против глобального правила.

> [!warning] Нюансы per-port варианта
> - **Только IPv4** (`table ip`). Для IPv6 нужен отдельный `table ip6`.
> - «Per-port = per-user» работает, **только если реально раздавать каждому свой порт**. Делят порт → делят бюджет.
> - Лимит считается по **порту назначения**, не по IP клиента — для приватного прокси это ок.

---

## Что с этим делать

### Диагностика — грепаем логи

```bash
# Сколько обрывов после ClientHello и с каких IP
docker compose logs telemt | grep "expected 64 got 0" | grep -oE '([0-9]+\.){3}[0-9]+' \
  | sort | uniq -c | sort -rn | head

# Привязка обрывов к фингерпринту (если JA4 в той же строке/рядом)
docker compose logs telemt | grep -E "expected 64 got 0|t13d" | tail -50
```

Если `expected_64_got_0` концентрируется на **одной подсети/операторе** и **одном JA4** — это подпись активного DPI, а не случайные сбои.

### Лечение — то же, что и для любого FakeTLS

Фингерпринт генерирует **приложение Telegram**, а не сервер — на стороне telemt его **не поменять** (нет uTLS, как в [[VLESS/dpi-tls-june-2026|REALITY]]). Единственное серверное лекарство — не дать DPI **собрать и опознать** ClientHello:

- **TCPMSS-дробление** — анонсировать малый MSS, чтобы клиент порезал ClientHello на куски, и stateful DPI не пересобрал фингерпринт.
- **nfqws TCP desync** (zapret) — fake-пакеты + TTL-limited split, чтобы сбить машину состояний DPI.

⚠️ В отличие от [[mtproxy/mtproto-zig|mtproto.zig]] (ставит это сам через `mtbuddy install`), **telemt этим не занимается** — обход на уровне ОС придётся накатывать руками поверх. Базовый рецепт TCPMSS+nfqws — в разборе [[mtproxy/mtproto-zig|mtproto.zig]] и в [[Zapret/about|zapret]].

- **Лимит SYN-ACK (1/сек)** — десинхронизирует stateful DPI и троттлит «залп». Спорный, но рабочий приём — см. [[#Лимит SYN-ACK — помогает или нет|раздел выше]]; бери сразу **per-port** вариант.
- **Сменить узел/подсеть**, если конкретный IP/диапазон попал под раздачу (Сигнал 1 [[VLESS/dpi-tls-june-2026|сибирской схемы]]).
- **Не дёргаться** — рефлекторная смена настроек под блоком сама по себе сигнал.

> [!summary] Главное
> 1. **Корень** (issue [#30733](https://github.com/telegramdesktop/tdesktop/issues/30733)): FakeTLS-фингерпринт Telegram **протух** (мимикрия под Chrome 134, а живой — 148). Чинится только в самом клиенте Telegram — оператор фингерпринт не меняет.
> 2. **Детект**: telemt 3.4.x логирует JA4 → `expected_64_got_0` + один фингерпринт + одна подсеть = таргетированный блок по версии клиента. Это твой бесплатный детектор DPI.
> 3. **Обход на стороне сервера** (фингерпринт не трогаем, прячем/ломаем опознание): дробление ClientHello (TCPMSS), nfqws-desync, лимит SYN-ACK (per-port), смена узла. Telemt сам это не ставит — накатывай поверх.

---

## 📚 См. также

- [[mtproxy/ja4-sni-client-side|Кто может менять JA4/SNI]] — почему смена почерка и ротация SNI возможны только на стороне клиента, а сервер бьёт лишь по «залпу»
- 🔗 [telegramdesktop/tdesktop#30733](https://github.com/telegramdesktop/tdesktop/issues/30733) — официальный issue: протухший FakeTLS-фингерпринт, блокировки с 22 мая
- [[Zapret/mtproto/03-telemt|03-telemt]] — установка и настройка telemt
- [[Zapret/mtproto/05-censorship|05-censorship]] — каскад детекции ТСПУ
- [[mtproxy/mtproto-zig|mtproto.zig]] — как дробление ClientHello и nfqws ставятся «под ключ»
- [[VLESS/dpi-tls-june-2026|Сибирская схема DPI: JA3/JA4, подсеть, частота]]
- [[DPI/dpi-analysis-pipeline|Воронка проверок DPI]]
- [[DPI/browser-ja4-fingerprint-block|Блокировка сайта по JA4-отпечатку браузера]] — тот же `…d8a2da3f94cd` ломает и обычные сайты в Chrome (кейс wireflow.space)
