v1.10.2

sleep3r/mtproto.zigv1.10.2Aug 1, 2026by sleep3r

Upgrading?

AI Summary

Fixes the tunnel failover watchdog timer, which could stop re-arming, and adds auto-healing for stale WireGuard tunnels.

Key Highlights

  • Fixed mtproto-tunnel-pool.timer stopping permanently.
  • Added auto-healing for stale WG sessions after consecutive failures.

New Features

  • Fixed pool timer
  • Auto-heal stale tunnels

Full Release Notes

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

## Коротко

`v1.10.2` чинит два связанных бага в фейловере тоннеля (`upstream.type = "tunnel"`, WireGuard/AmneziaWG egress): watchdog-таймер health-check мог остановиться навсегда и никогда не перезапуститься сам, а протухший (но формально «up») тоннель мог использоваться бесконечно без попытки самовосстановления.

Ядро MTProto-прокси и сетевой протокол не менялись. Обновление безопасно и рекомендуется всем, кто использует `upstream.type = "tunnel"`.

## Таймер `mtproto-tunnel-pool.timer` останавливался навсегда

**Симптом:** обнаружено вживую на проде (`proxy.sleep3r.ru`) — прокси переставал доставать до Telegram, хотя `systemctl status mtproto-tunnel-pool.timer` показывал `active`. `LastTriggerUSec` был заморожен на дате более чем десятидневной давности, `NextElapseUSecRealtime` — пустой: сам failover-watchdog фактически не работал уже давно.

**Причина:** таймер стоял на связке `OnBootSec=30s` + `OnUnitInactiveSec=30s`. `OnUnitInactiveSec` перевзводится от перехода связанного `.service` в inactive; после обычного `systemctl restart` самого таймера (без ребута хоста) `OnBootSec` больше не сработает — он привязан к уже прошедшему бут-циклу, — а `OnUnitInactiveSec` не имеет свежего перехода, от которого оттолкнуться. Таймер замолкает навсегда. Воспроизведено вживую: `systemctl restart` таймера не помогал, а разовый ручной `systemctl start` самого `.service` давал `OnUnitInactiveSec` нужный «якорь», и таймер снова затикал — это подтвердило причину и легло в основу фикса.

**Фикс:** таймер переведён на `OnCalendar=*-*-* *:*:00/30` — расписание пересчитывается от wall-clock при каждом (ре)старте юнита и не зависит от истории состояния связанного сервиса, так что застрять описанным образом больше не может.

## Протухший тоннель использовался вечно вместо самовосстановления

**Симптом:** health-check пула уже умел определять «тоннель up, но Telegram через него не достаётся» — но при единственном тоннеле в пуле фейловериться было не с кем, и скрипт просто продолжал маршрутизировать через тот же деградировавший тоннель, помечая статус `degraded`. Тот же класс проблемы, что VPN на телефоне, который выглядит подключённым, но не работает, пока его не выключить и включить руками.

**Причина:** проверка интерфейса поднимала его, только если он вообще отсутствовал; если интерфейс был на месте, а WG-сессия внутри протухла (например, после сетевого сбоя), её никто и никогда не передёргивал заново.

**Фикс:** добавлен счётчик подряд идущих «degraded»-проверок на интерфейс. После `BOUNCE_AFTER_DEGRADED_CHECKS` (2, то есть ~60с при 30-секундном такте) подряд неудачных проб тоннель один раз передёргивается (`awg-quick`/`wg-quick` down → up), чтобы форсировать свежий handshake, и перепроверяется — прежде чем соглашаться на деградированный fallback. Порог намеренно выше одного цикла, чтобы разовый всплеск не дёргал здоровый тоннель; счётчик сбрасывается при успехе или после попытки bounce, так что по-настоящему заблокированный маршрут не будет передёргиваться каждый цикл бесконечно.

## Проверено

- `zig build test`: 269/269 зелёные; кросс-сборка `x86_64-linux` — чистая.
- Задеплоено и подтверждено на живом проде (`proxy.sleep3r.ru`): после обновления таймер сразу показал корректный пересчитанный `Trigger` (не пустой `NextElapseUSecRealtime`, как раньше), несколько циклов подряд отработали чисто с новым скриптом, тоннель остался `healthy`.

## Changelog

- fix(ctl): repair dead pool-failover timer + auto-heal stale WG sessions ([#384](https://github.com/sleep3r/mtproto.zig/pull/384))

</details>

---

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

## TL;DR

`v1.10.2` fixes two related bugs in the tunnel failover path (`upstream.type = "tunnel"`, WireGuard/AmneziaWG egress): the health-check watchdog timer could stop for good and never re-arm itself, and a stale-but-nominally-up tunnel could be used forever without ever attempting to self-heal.

The MTProto proxy core and wire protocol are unchanged. Upgrading is safe and recommended for anyone running `upstream.type = "tunnel"`.

## `mtproto-tunnel-pool.timer` stopped re-arming for good

**Symptom:** found live on prod (`proxy.sleep3r.ru`) — the proxy stopped reaching Telegram even though `systemctl status mtproto-tunnel-pool.timer` reported `active`. `LastTriggerUSec` was frozen more than ten days in the past, and `NextElapseUSecRealtime` was empty: the failover watchdog had effectively been dead the whole time.

**Cause:** the timer used `OnBootSec=30s` + `OnUnitInactiveSec=30s`. `OnUnitInactiveSec` re-arms off the paired `.service`'s own inactive-transition; after a plain `systemctl restart` of just the timer (no host reboot), `OnBootSec` won't fire again — it's tied to a boot cycle that already happened — and `OnUnitInactiveSec` has no fresh transition to key off of. The timer goes silent for good. Reproduced live: restarting the timer alone never helped, while manually running the paired `.service` once gave `OnUnitInactiveSec` the anchor it needed and the timer started ticking again — confirming the cause and the fix.

**Fix:** the timer now uses `OnCalendar=*-*-* *:*:00/30`, which recomputes from wall-clock on every (re)start instead of chaining off the paired unit's state history, so it can't get stuck this way again.

## A stale tunnel was used forever instead of self-healing

**Symptom:** the pool health-check could already detect "tunnel up, but Telegram unreachable through it" — but with a single tunnel in the pool there was nothing to fail over to, so the script just kept routing through the same degraded tunnel, marking it `degraded`. The same failure class as a phone VPN that looks connected but passes nothing until manually toggled off and on.

**Cause:** the interface check only brought the interface up if it was missing entirely; if the interface was present but its WG session had gone stale (e.g. after a network hiccup), nothing ever bounced it.

**Fix:** added a consecutive-degraded-checks counter per interface. After `BOUNCE_AFTER_DEGRADED_CHECKS` (2, ~60s at the 30s cadence) failed probes in a row, the tunnel is bounced once (`awg-quick`/`wg-quick` down → up) to force a fresh handshake and re-probed, before falling back to the degraded state. The threshold is kept above a single cycle so one transient probe blip doesn't churn a healthy tunnel; the counter resets on success or after an attempted bounce, so a genuinely-blocked route isn't bounced every cycle forever.

## Verified

- `zig build test`: 269/269 pass; `x86_64-linux` cross-compile is clean.
- Deployed and confirmed on live prod (`proxy.sleep3r.ru`): after the update the timer immediately showed a correctly recomputed `Trigger` (no longer an empty `NextElapseUSecRealtime`), several cycles ran clean with the new script, and the tunnel stayed `healthy`.

## Changelog

- fix(ctl): repair dead pool-failover timer + auto-heal stale WG sessions ([#384](https://github.com/sleep3r/mtproto.zig/pull/384))

</details>

---

**Full Changelog**: https://github.com/sleep3r/mtproto.zig/compare/v1.10.1...v1.10.2

Related Repositories