fix: диагностика перестаёт врать — падения маршрутов и потеря секрета (#38)

Два дефекта одного класса: состояние, которое не отражает реальность. Оба
найдены по данным из #32 и #37.

#32. Присланный лог показывал «сбоев 0» при том, что DC2 и DC4 всегда шли через
«Запасной Telegram IP», а DC203 всегда через «Системный DNS» — то есть
основные закреплённые адреса не использовались ни разу. Причина в счётчике:
ws_failures растёт только когда упали ВСЕ маршруты и соединение не состоялось.
Падения отдельных маршрутов через record_failure не попадали никуда, поэтому
перебор с откатом на запасной адрес выглядел как полное отсутствие проблем.

Добавлен route_failures: растёт на каждое падение маршрута, виден в строке
статуса CLI и в диагностике интерфейса. Теперь по логу сразу видно, что
закреплённый адрес мёртв, а не приходится это выводить.

#37. Симптом: Telegram пишет «прокси настроен неверно и будет отключён», при
этом Check status показывает Available. Это картина несовпадения секрета: TCP
проходит, init не разбирается под другим секретом, соединение закрывается.

Секрет мог меняться молча:

  #[cfg(not(unix))]
  fn write_secret_file(path: &Path, value: &str) {
      let _ = std::fs::write(path, value);   // ошибка выброшена
  }

create_dir_all рядом — так же. Если запись в %APPDATA%\TGLock\secret не
удавалась, программа генерировала новый секрет при каждом запуске и ничего об
этом не сообщала.

Теперь write_secret_file возвращает Result, load_or_create_secret_at отдаёт
StoredSecret с полем write_error, а оба интерфейса показывают предупреждение:
CLI строкой при старте, GUI записью в журнал. Прокси при этом продолжает
работать — просто до перезапуска.

Тесты: every_route_failure_is_counted, a_failed_write_is_reported_instead_of
_swallowed (родитель пути — файл, поэтому каталог не создать),
a_successful_write_reports_no_error (секрет переживает второй запуск).

В диагностике интерфейса добавлена подсказка: падения маршрутов больше нуля
при работающем Telegram — норма, значит закреплённый адрес недоступен и
подключение идёт через запасной.

Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com>
This commit is contained in:
Никита Sonic
2026-08-04 19:43:21 +03:00
committed by GitHub
parent 8491cf9292
commit fe85784550
7 changed files with 231 additions and 29 deletions
+13 -2
View File
@@ -142,6 +142,15 @@ async fn serve(args: Args) -> Result<(), String> {
"Ссылка для Telegram: {}",
listen.telegram_link(&stats.telegram_secret())
));
// Запись секрета могла провалиться — тогда после перезапуска ссылка
// изменится и Telegram скажет «прокси настроен неверно». Раньше это
// происходило молча (by-sonic/tglock#37).
if let Some(error) = stats.secret_write_error() {
say(&format!(
"Внимание: секрет НЕ сохранён ({error}). После перезапуска ссылка \
изменится, и Telegram откажется подключаться к старой"
));
}
if matches!(settings.secret, config::SecretSource::Ephemeral) {
say(
"Внимание: секрет не закреплён и будет новым после перезапуска — \
@@ -202,13 +211,15 @@ async fn watch_status(stats: Arc<proxy::Stats>) {
stats.last_dc.load(Ordering::Relaxed),
stats.last_route.load(Ordering::Relaxed),
stats.ws_failures.load(Ordering::Relaxed),
stats.route_failures(),
);
if previous.as_ref() == Some(&current) {
continue;
}
let (active, tunnels, dc, route, failures) = current;
let (active, tunnels, dc, route, failures, route_failures) = current;
let line = format!(
"соединений {active} · туннелей {tunnels} · {} · {} · сбоев {failures}",
"соединений {active} · туннелей {tunnels} · {} · {} · сбоев {failures} · \
падений маршрутов {route_failures}",
if dc > 0 {
format!("DC{dc}")
} else {