v1.7.0

sleep3r/mtproto.zigv1.7.0Jun 12, 2026by sleep3r

Upgrading?

AI Summary

Fixes the iOS 'Updating' hang on resume by identifying the root cause as a client-side salt expiration issue and adding an opt-in workaround. Removes a previously added configuration option.

Key Highlights

  • Root cause identified as an iOS client salt expiration bug
  • New opt-in configuration option `client_silence_close_sec` (default 0)
  • Removal of deprecated `max_connection_lifetime_sec` config key
  • Verified on live production environment

New Features

  • New `client_silence_close_sec` configuration option for proxy-side timeout handling

Full Release Notes

<details open>
<summary>🇷🇺 Что нового (RU)</summary>

## Коротко

`v1.7.0` доводит до конца историю с зависанием iOS в «обновлении» после фона. Гипотеза из `v1.6.0` (коллапс congestion window) **оказалась неверной** и опровергнута живой диагностикой; здесь — настоящая причина и точечный воркэраунд. Поведение по умолчанию не меняется (фича выключена), апгрейд безопасен.

## Что нашли (настоящая причина)

Это **баг клиента iOS**, не прокси. После долгого фона у аккаунта протухает server salt. DC отвечает `bad_server_salt` за ~280мс и **сразу кладёт туда новую валидную соль**, но MtProtoKit применяет её только если отвергнут специальный time-fix ping; на обычный запрос соль **выбрасывается**, сессия встаёт в `MTProtoStateAwaitingTimeFixAndSalts`, и ни один запрос из очереди не уходит. Выйти нечем (у time-fix контекста нет таймаута, 12с/5с таймеры разармятся входящим ответом, sleep-watchdog закомментирован с 2014). Сессия молчит, пока DC сам не закроет сокет (~90–120с) — это и есть бесконечное «обновление». На Android (tgnet) этого нет, он гасит idle-коннекты через ~10с фона.

Апстрим-PR с настоящим фиксом (применять соль из `bad_server_salt` всегда): **TelegramMessenger/Telegram-iOS#2197**.

## Что добавили (воркэраунд на стороне прокси)

Новая opt-in опция **`client_silence_close_sec`** (по умолчанию `0` = выкл). Закрывает релей, если последним по проводу был ответ сервера, а клиент не ответил N секунд — это форсит чистый реконнект (~450мс), который у iOS как раз снимает затык. Срабатывает только когда последним был s2c, поэтому здоровый idle-коннект (чьё последнее слово — свой пинг/ack) не трогается. На проде с порогом 10с «обновление» вместо ~90с проходит за ~10с. Это **best-effort**, не лечение: прокси слеп к шифрованному MTProto, поэтому значение ниже самого медленного легитимного ответа изредка закроет и здоровый коннект (лишний ~450мс реконнект). Настраивается под себя.

```toml
[server]
client_silence_close_sec = 10   # 0 = выкл (дефолт); ~10-15 как отправная точка
```

## Удалено

`max_connection_lifetime_sec` (из `v1.6.0`) убран — он бил по неверной гипотезе. Неизвестные ключи конфига игнорируются, так что оставшаяся строка безвредна; замена — `client_silence_close_sec`.

## Проверено

`zig build test` зелёный + кросс-сборки + install-матрица (debian 11/12, ubuntu 20.04/22.04/24.04) + E2E + bench. Воркэраунд подтверждён на живом проде.

</details>

---

<details>
<summary>🇬🇧 Release notes (EN)</summary>

## TL;DR

`v1.7.0` closes out the iOS "Updating" hang on resume. The `v1.6.0` hypothesis (congestion-window collapse) was **wrong** and disproven by live diagnostics; this ships the real root cause plus a targeted workaround. Defaults are unchanged (the feature is off), upgrading is safe.

## What it actually is

An **iOS client bug**, not the proxy. After a long background the account's cached server salt is stale. The DC replies `bad_server_salt` in ~280ms with a fresh valid salt, but MtProtoKit only applies it when the rejected message is the dedicated time-fix ping; for an ordinary request the salt is **discarded** and the session enters `MTProtoStateAwaitingTimeFixAndSalts`, which gates every outgoing transaction. Nothing recovers it (the time-fix context has no timeout, the 12s/5s watchdogs disarm on the incoming reply, the sleep watchdog is `#if false` since 2014), so the session is silent until the DC closes the socket (~90–120s) — the infinite "Updating". Android (tgnet) avoids it by tearing down idle connections ~10s into background.

Upstream PR with the real fix (apply the salt from `bad_server_salt` unconditionally): **TelegramMessenger/Telegram-iOS#2197**.

## What this adds (proxy-side workaround)

New opt-in option **`client_silence_close_sec`** (default `0` = off). Closes a relay when the last relayed payload was server→client and the client hasn't answered for N seconds, forcing the clean reconnect (~450ms) that recovers the iOS client. It fires only when the server spoke last, so a healthy idle connection (whose last word was its own ping/ack) is never touched. On prod at 10s, "Updating" clears in ~10s instead of ~90s. It is **best-effort**, not a cure: the proxy can't read the encrypted MTProto stream, so a value below your slowest legitimate response will occasionally close a healthy connection too (just a ~450ms reconnect). Tune to taste.

```toml
[server]
client_silence_close_sec = 10   # 0 = off (default); ~10-15 as a starting point
```

## Removed

`max_connection_lifetime_sec` (from `v1.6.0`) is gone — it targeted the wrong hypothesis. Unknown config keys are ignored, so a leftover entry is harmless; its replacement is `client_silence_close_sec`.

## Verified

`zig build test` green + cross-builds + install matrix (debian 11/12, ubuntu 20.04/22.04/24.04) + E2E + bench. The workaround is confirmed on live prod.

</details>

---

**Full Changelog**: https://github.com/sleep3r/mtproto.zig/compare/v1.6.0...v1.7.0

Related Repositories