todo/Zapret/mtproto/10-telemt-logs-dpi.md
loop-uh 0ad0bcc8dd
Some checks failed
Published content check / validate (push) Failing after 5s
seo+зеркало: [!mirror]-плашка со ссылкой на вики и description во frontmatter всех заметок
Каждая публикуемая заметка получила callout-шапку со ссылкой на свою страницу
wiki.zapret.moe (на самой вики она вырезается транформером RemoveMirrorCallout,
видна только на зеркале Obsidian Publish и в Forgejo) и SEO-поле description —
1–2 предложения для meta description обоих сайтов.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 22:05:57 +03:00

255 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
description: "Чтение логов telemt 3.4.x: ошибка expected_64_got_0, JA4-фингерпринты клиентов Telegram, детект блокировок ТСПУ и обход лимитом SYN-ACK."
---
> [!mirror] Резервное зеркало
> Актуальная версия этой страницы — на основной вики: [wiki.zapret.moe/Zapret/mtproto/10-telemt-logs-dpi](https://wiki.zapret.moe/Zapret/mtproto/10-telemt-logs-dpi)
# 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)