github sleep3r/mtproto.zig v1.0.1

latest releases: v1.10.3, v1.10.2, v1.10.1...
2 months ago
🇷🇺 Что нового (RU)

Что решает этот релиз

v1.0.1 — патч-релиз для middleproxy-режима (use_middle_proxy = true), который чинит коварный сценарий: прокси выглядит полностью здоровым, но клиенты не подключаются.

Production-инцидент выглядел так: процесс жив, слушает :443, туннель в норме (свежий handshake, до DC по TCP достукивается) — но users_total=0, и каждое соединение уходит в hs_timeout. Клиенты проходят FakeTLS, но застревают на upstream RPC-handshake с middleproxy, который перестал отвечать.

Причина: метаданные MiddleProxy (адреса DC из getProxyConfig + секрет из getProxySecret) обновлялись по таймеру раз в 24 часа, а Telegram ротирует их быстрее — в нашем случае dc4 уехал 91.108.4.139 → 91.108.4.200 примерно через 10 часов после загрузки на старте. Кэш протух, и весь middleproxy-трафик встал до ручного рестарта.

v1.0.1 обновляет метаданные часто (раз в час), а при первых же зависших handshake'ах — немедленно (реактивно), и заодно делает саму загрузку метаданных устойчивой к битому /etc/resolv.conf.

[!NOTE]
Менять config.toml не нужно — фикс работает на текущих настройках. Затрагивает только middleproxy-режим (use_middle_proxy = true); чисто обфусцированных / FakeTLS-без-middleproxy деплоев это не касается.

[!TIP]
Что реактивное обновление сработало, видно в логах — строка Middle-proxy reactive refresh: ..., а следом Middle-proxy cache updated: dc4=... secret_len=128.

Что изменено

Метаданные MiddleProxy обновляются раз в час, а не раз в сутки (#305)

  • Период периодического обновления (middle_proxy_update_period_ns) сокращён с 24 часов до 1 часа.
  • Telegram ротирует адреса DC и секрет в пределах суток (в инциденте — за ~10 часов), поэтому суточная частота позволяла кэшу протухнуть и обрушить весь middleproxy-трафик до рестарта.
  • Загрузка дешёвая и best-effort: при сетевом сбое прокси держит текущий кэш, так что дополнительные опросы стоят практически ничего.
  • Худший случай протухания сужен с суток до часа.

Реактивное обновление при зависших handshake'ах (#305)

  • Если соединение уходит в hs_timeout, всё ещё находясь в RPC-handshake с middleproxy, это прямой признак, что кэш протух — data-plane просит updater обновиться не дожидаясь часового таймера.
  • Протухший DC/секрет заменяется за ~handshake_timeout + одну загрузку (десятки секунд) вместо «до часа».
  • Дебаунс 60 секунд: шквал падающих handshake'ов — или реальный сбой на стороне middleproxy, который свежие метаданные не починят — не устроит refresh-шторм.
  • Реализовано lock-free (атомарный флаг на data-plane, который читает wait-цикл updater'а); на горячий путь ретрансляции не влияет.

Загрузка метаданных устойчива к битому resolv.conf (#305)

  • Стандартный резолвер Zig кидает ResolvConfParseFailed на /etc/resolv.conf без завершающего перевода строки (SolusVM и ряд VPS-образов) — из-за чего каждая загрузка логировала unreachable directly … retrying via tunnel.
  • Теперь именно на эту ошибку загрузка делает фоллбэк на curl (резолв через NSS/getent, который такой файл переваривает).
  • Бездепендентный std-путь сохранён для здоровых хостов; для middleproxy с прямым egress на таких хостах загрузка метаданных перестаёт быть сломанной, а шум в логах уходит.

Проверено

  • zig build test (включая новый тест предиката MiddleProxyHandshakeStep.awaitingMiddleProxy) и кросс-компиляция под x86_64-linux + aarch64-linux — зелёные; zig fmt чистый.
  • GitHub Actions: Test & Build, E2E Integration, Bench (No Soak).
  • Инцидент диагностирован и восстановлен на живом деплое (proxy.sleep3r.ru): подтянулись свежие dc4=91.108.4.200 + секрет, users_total снова растёт, hs_timeout пропал.

🇬🇧 Release notes (EN)

What this release addresses

v1.0.1 is a patch release for middleproxy mode (use_middle_proxy = true) that fixes a sneaky failure: the proxy looks completely healthy, yet clients can't connect.

The production incident looked like this: the process is up and listening on :443, the tunnel is fine (fresh handshake, DCs reachable over TCP) — but users_total=0 and every connection hits hs_timeout. Clients pass FakeTLS but then stall on the upstream RPC handshake with a middleproxy that stopped answering.

Root cause: the MiddleProxy metadata (getProxyConfig DC addresses + getProxySecret) refreshed on a 24-hour timer, but Telegram rotates it faster — here the dc4 media-DC address moved 91.108.4.139 → 91.108.4.200 about 10 hours after the startup fetch. The cache went stale and stalled all middleproxy traffic until a manual restart.

v1.0.1 refreshes the metadata often (hourly), refreshes it immediately (reactively) at the first stalled handshakes, and makes the fetch itself resilient to a malformed /etc/resolv.conf.

[!NOTE]
No config.toml changes needed — the fix works on your current settings. It only affects middleproxy mode (use_middle_proxy = true); plain-obfuscated / FakeTLS-without-middleproxy deployments are unaffected.

[!TIP]
You can see the reactive refresh fire in the logs — the line Middle-proxy reactive refresh: ... followed by Middle-proxy cache updated: dc4=... secret_len=128.

What changed

MiddleProxy metadata refreshes hourly, not daily (#305)

  • The periodic refresh period (middle_proxy_update_period_ns) is cut from 24 hours to 1 hour.
  • Telegram rotates the DC addresses and secret within a day (~10 hours in the incident), so the daily cadence let the cache go stale and stall all middleproxy traffic until a restart.
  • The fetch is cheap and best-effort: on a network failure the proxy keeps its current cache, so the extra polls cost effectively nothing.
  • Bounds the worst-case stale window from a day to an hour.

Reactive refresh on stalled handshakes (#305)

  • When a connection hits hs_timeout while still in the RPC handshake with the middleproxy, that's a direct sign the cache went stale — the data plane asks the updater to refresh without waiting for the hourly timer.
  • A stale DC/secret is replaced in ~handshake_timeout + one fetch (tens of seconds) instead of up to an hour.
  • Debounced by 60 seconds: a flood of failing handshakes — or a genuine middleproxy-side outage that fresh metadata can't fix — can't cause a refresh storm.
  • Implemented lock-free (an atomic flag on the data plane, read by the updater's wait loop); no effect on the relay hot path.

Metadata fetch resilient to a malformed resolv.conf (#305)

  • Zig's std resolver throws ResolvConfParseFailed on a /etc/resolv.conf without a trailing newline (SolusVM and several VPS images) — which is why every fetch logged unreachable directly … retrying via tunnel.
  • On exactly that error, the fetch now falls back to curl (NSS/getent resolution, which tolerates that file).
  • The dependency-free std path is kept for healthy hosts; direct-egress middleproxy on such hosts stops being broken, and the log noise goes away.

Verified

  • zig build test (including a new MiddleProxyHandshakeStep.awaitingMiddleProxy predicate test) and cross-compilation for x86_64-linux + aarch64-linux — green; zig fmt clean.
  • GitHub Actions: Test & Build, E2E Integration, Bench (No Soak).
  • The incident was diagnosed and restored on the live deployment (proxy.sleep3r.ru): fresh dc4=91.108.4.200 + secret pulled, users_total climbing again, hs_timeout gone.

Changelog

  • fix(middleproxy): hourly + reactive refresh, and a getent fallback on the metadata fetch (#305) (86ccf03)

Don't miss a new mtproto.zig release

NewReleases is sending notifications on new releases.