Some checks failed
Published content check / validate (push) Failing after 5s
Каждая публикуемая заметка получила callout-шапку со ссылкой на свою страницу wiki.zapret.moe (на самой вики она вырезается транформером RemoveMirrorCallout, видна только на зеркале Obsidian Publish и в Forgejo) и SEO-поле description — 1–2 предложения для meta description обоих сайтов. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
255 lines
24 KiB
Markdown
255 lines
24 KiB
Markdown
---
|
||
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)
|