mirror of
https://github.com/by-sonic/tglock.git
synced 2026-09-06 02:26:08 +03:00
fe9ab39abea5b59a411a89afe573cf68878b7bcd
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b92403b4c9 |
fix(proxy): клиент, с которым не договорились, закрывался молча (#42) (#45)
Диагностика от @alexsagaidak в #42 показала третий случай, которого ни один счётчик не различал. У него ноль отклонённых и живые туннели, то есть оба показателя говорят «всё хорошо»: соединений 17 · туннелей 6 · DC4 · Запасной Telegram IP · сбоев 0 · падений маршрутов 26 · отклонено 0 Клиент, который дошёл до прокси, но не сумел договориться, не попадал ни в blocked, ни в ws_failures. Ошибка из handle() выбрасывалась в `let _ =`, соединение закрывалось, и наружу это выглядело как active, дёрнувшийся вверх и обратно. По диагностике неотличимо от клиента, который подключился и работает. Теперь такие клиенты считает unknown_clients, а журнал называет адрес и причину. Причин две: MTProto-init не разбирается под текущим секретом. Почти всегда это ссылка tg://proxy от прошлого запуска: секрет — её половина, и клиент с сохранённой старой ссылкой попадает ровно сюда. Со стороны Telegram это и есть «прокси настроен неверно и будет отключён» — то, с чем пришли в #37 и что до сих пор нельзя было подтвердить со стороны прокси. SOCKS5-приветствие не разбирается. Сюда же попадает MTProto-соединение, ушедшее в SOCKS5-ветку по неоднозначному первому байту, если полный init не успел прийти за PROTOCOL_PROBE_TIMEOUT. На loopback этого не бывает, а через Wi-Fi с телефона — уже вопрос задержки. От ws_failures отличается тем, что тот растёт после успешного рукопожатия с клиентом: там договорились с клиентом, но не смогли с Telegram. Различать их важно, иначе непонятно, в какую сторону смотреть. В интерфейсе — метрика «Не опознаны» с пояснением, в строке статуса CLI — поле «не опознано N». Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com> |
||
|
|
945e794eb3 |
fix(proxy): LAN-режим отклонял настоящие адреса Telegram (#42) (#43)
@alexsagaidak: с компьютера прокси работает, с телефона — нет. Адрес машины
вписан верно, порт верный, брандмауэр выключен, порт добавлен в исключения.
Причина нашлась в проверке «это Telegram?». Она сравнивала два первых октета:
(149, 154) => Some(...)
(91, 108) => Some(...)
То есть «телеграмом» считались целиком четыре /16 — десятки тысяч чужих
адресов, — а IPv6 не распознавался вообще ни один. Telegram владеет
149.154.160.0/20, шестью /22 в 91.108, 91.105.192.0/23, 185.76.151.0/24 и пятью
блоками IPv6.
Ошибка в обе стороны, и на loopback она не видна. Там allow_direct включён, и
неопознанный адрес всё равно релеится напрямую — соединение просто работает.
На сетевом слушателе allow_direct выключен, и тот же адрес получает SOCKS5
0x02. Отсюда ровно то, что описал репортёр: на компьютере работает, с телефона
нет. Заодно чужие адреса внутри этих /16 уходили в MTProto-туннель и умирали
там.
Список сетей теперь опубликованный самим Telegram
(core.telegram.org/resources/cidr.txt), проверка по маске префикса, IPv4 и
IPv6, с тестами на края блоков и на соседей за границей.
Отдельно разрешены имена веб-инфраструктуры: telegram.org, t.me, telegram.me,
telesco.pe, cdn-telegram.org. Это не MTProto, а обычный HTTPS — клиент ходит
туда за конфигурацией, превью и файлами CDN, и на телефоне эти запросы идут
через тот же прокси. Заворачивать их в туннель нельзя, поэтому они идут прямым
релеем. Совпадение по границе метки, так что telegram.org.example.com —
посторонний домен. В ограниченном режиме имя разрешается заранее и адреса
внутри локальной сети отбрасываются: назначение выбирает чужое устройство, и
DNS-ответ не должен открывать доступ к 192.168.х этой машины.
И главное для разбора следующего такого случая: отказ перестал быть
молчаливым. Появился счётчик «Отклонено» в диагностике и в строке статуса CLI,
отклонённый адрес один раз называется в журнале, и отдельно отмечается первое
подключение с каждого сетевого адреса. Без этого «с телефона не работает»
неразличимо распадалось на два случая: телефон не дошёл до машины — и дошёл,
но попросил адрес, который мы не пускаем. Теперь первый виден как ноль
соединений и ноль отказов, второй — как соединения есть, отказы растут.
Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com>
|
||
|
|
fe85784550 |
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> |
||
|
|
9429975403 |
feat(cli): файл настроек tglock.toml (#35)
* fix(bundle): спрятать CLI за фичей — beta.2 и beta.3 упаковывали не тот бинарь Мой предыдущий фикс (mainBinaryName в #31) фиксом не был. Он изменил только имя выходного файла: бандлер по-прежнему брал headless CLI и просто переименовывал его в tglock. Проверено по содержимому опубликованных артефактов, а не по имени: версия файл в MacOS/ размер признаки CLI признаки GUI beta.1 tglock 17.8 МБ нет ipc.localhost, wry×3206 beta.2 tglock-cli 4.3 МБ tglock-cli×6 нет beta.3 tglock 4.3 МБ tglock-cli×6 нет То есть beta.3 тоже не запускается, и моя же проверка CFBundleExecutable это пропустила, потому что сверяла имя. Настоящая причина найдена воспроизведением локально. Ломается только при явном --target: без него бандлер выбирает GUI, с ним — CLI. Локальная сборка, на которой я объявил фикс подтверждённым, шла без --target, а обе сборки в CI — с ним. Обобщение было неправомерным. Исправление убирает саму возможность выбора: CLI спрятан за фичей cli, которой нет в default. При сборке приложения второго бинаря просто не существует. Проверено против воспроизведённой поломки: до : tglock.exe 1.7 МБ, признаки CLI, GUI нет после: tglock.exe 9.0 МБ, признаки GUI, CLI нет, tglock-cli.exe не собран Проверки переписаны на содержимое: - release.yml распаковывает .app и .deb и ищет ipc.localhost (есть только в GUI) и allow-direct (есть только в CLI). Поймала бы и beta.2, и beta.3; - новая задача CI bundle собирает бандл с явным --target aarch64-apple-darwin и проверяет его так же. Ловит до публикации, а не после; - заодно исправлен шаблон grep для .deb: dpkg-deb -c выводит путь без ./, из-за чего проверка ложно падала на исправной сборке. Команда сборки CLI теперь требует --features cli; обновлены README, ci.yml и release.yml. * fix(ci): одна проверка бандла на Python вместо трёх копий grep Проверка содержимого бандла, добавленная в #33, работала на Linux и давала ложное «в бандле не GUI» на macOS. Причина в BSD grep: в UTF-8-локали он молча не находит строки в бинарных данных там, где GNU grep находит. Из-за этого задача macOS в релизе beta.4 упала уже после загрузки артефактов, а вместе с ней снова пропустилась задача с CLI-бинарями. Сами артефакты beta.4 при этом корректны — проверено скачиванием: в бандле tglock на 20.3 МБ, universal/fat, признаки GUI на месте, признаков CLI нет. Не хватает только tglock-cli-*. Проверка вынесена в scripts/verify_bundle_binary.py и вызывается из всех трёх мест: задачи CI bundle, проверки macOS и проверки Linux в релизе. Один скрипт вместо трёх копий шелл-кода исключает и платформенные различия grep, и расхождение копий между собой. Скрипт проверен на реальных исторических артефактах: beta.4 (исправный) → код 0 beta.3 (сломанный) → код 1, найдены allow-direct и secret-file beta.2 (сломанный) → код 1, то же * feat(cli): файл настроек tglock.toml Запрошено в #32: держать все параметры и секрет в одном месте, чтобы не собирать батник с ключами при каждом запуске. Файл ищется рядом с бинарём под именем tglock.toml — как и просили в ишью, — либо указывается через --config. Приоритеты: значения по умолчанию → файл → флаги. Флаги-переключатели могут только включать: отсутствие --quiet не отменяет quiet = true из файла, иначе файлом нельзя было бы ничего включить. Секрет можно вписать прямо в конфиг, в том числе в форме с префиксом dd — то есть скопировав из напечатанной ссылки tg://proxy. Отдельный secret_file остаётся, inline-секрет важнее. deny_unknown_fields намеренно: опечатка вроде porrt = 1443 останавливает старт с перечислением допустимых полей. Сервис, который из-за опечатки слушает 1080 вместо 1443, хуже сервиса, который не запустился. Явный --config к несуществующему файлу тоже ошибка, а не тихий откат к настройкам по умолчанию. Если секрет не закреплён ни одним из способов, CLI печатает предупреждение: после перезапуска ссылка станет другой и настроенные клиенты отвалятся. Найдено при живом прогоне и исправлено здесь же: демон падал, если stdout закрывался. println! при ошибке записи паникует, а канал закрывается штатно — `| head`, закрытый терминал, перезапуск сборщика логов. Воспроизводилось одной командой: tglock-cli --port 18101 | head -2 thread 'tokio-rt-worker' panicked at stdio.rs: failed printing to stdout Печать переведена на say(), который возвращает признак успеха; наблюдатель статуса при закрытом stdout просто прекращает печатать, туннель продолжает работать. Тестов в CLI стало 26 вместо 12: приоритеты, конфликт lan и bind из разных источников, обе формы записи секрета, отказ на опечатке и на битых значениях, комментарии в файле. Плюс parse_secret в ядре с проверкой того, что секрет, сам начинающийся с dd, не теряет первый байт. В CI добавлены три шага: чтение настроек из файла, перекрытие флагом и отказ на опечатке; отдельно — проверка, что закрытый stdout не роняет демон. Документация: секция про файл настроек в README, полный пример с пояснениями в tglock.example.toml, юнит systemd переведён на --config. * fix: вернуть плоский путь бинаря, модуль настроек — в библиотеку Проверка бандла в CI упала на этом PR: failed to rename app binary .../release/cli: No such file or directory Причина моя. Я перенёс CLI в каталожную форму src/bin/cli/main.rs, и перечисление бинарей в Tauri вывело имя приложения из имени каталога — получилось "cli", которого не существует. Явное name = "tglock-cli" в [[bin]] при этом игнорируется. До переноса, при плоском src/bin/cli.rs, всё собиралось. Путь бинаря вернулся к src/bin/cli.rs, а модуль настроек переехал в библиотеку как cli_settings под фичей cli. Ему там и место: он работает с ListenConfig, mtproto и proxy, а clap не использует вовсе. Проверено воспроизведением того же условия локально — сборка бандла с явным --target: приложение собирается, скрипт проверки подтверждает GUI-бинарь (9.0 МБ, ipc.localhost и wry на месте, признаков CLI нет). Тестов: 66 в библиотеке с фичей cli, 12 в бинаре, clippy чист в обоих вариантах сборки. Живой прогон с конфигом и отказ на опечатке сохранились. --------- Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com> |
||
|
|
55653ed0bc |
feat(cli): headless tglock-cli, GUI behind a feature, deep test coverage (#25)
Реализует направление PR #15 поверх архитектуры 2.0. Сам PR смерджить нельзя: он патчит src/bypass.rs, src/network.rs и src/ws_proxy.rs, которых больше нет, и правит системный DNS — в 2.0 это не нужно, потому что адреса Telegram зашиты в маршрутах, а SNI остаётся настоящим. Взято разделение GUI/CLI и произвольный bind-адрес, отброшены DNS-менеджмент и проверка root: CLI не требует прав. Closes #10, #17 — GUI не создаёт окно без 3D-ускорения, на машине без монитора и в виртуалке. Причина в WebView под Tauri, поэтому лечится не программным рендером, а бинарём, в котором WebView нет вовсе: при выключенной фиче gui Tauri и фронтенд в сборку не попадают. Отдельная задача CI собирает и гоняет CLI на голом ubuntu без Node.js и без libwebkit2gtk. Структура: - src/lib.rs — ядро (mtproto, proxy, transport, config), без Tauri - src/main.rs — GUI, required-features = ["gui"] - src/bin/cli.rs — headless-бинарь на clap - build.rs вызывает tauri_build только при включённой фиче gui Исправлено по пути: - Определение протокола: SOCKS5 и MTProto различались по первому байту, но is_reserved_init не исключает 0x05, поэтому примерно одно соединение из 256 уезжало в SOCKS5-ветку и умирало. Теперь неоднозначный первый байт решается по полному 64-байтному init и секрету. - Ярлык маршрута в UI: код 2 подписывался как «Cloudflare Worker», хотя это запасной Telegram IP, а системный DNS и настоящий Worker оба показывались как «Автоматический маршрут». Метки переехали в transport::route_label, общий для обоих интерфейсов. - Секрет прокси: под DynamicUser и ProtectHome домашней папки нет, secret_path возвращает None и секрет генерировался заново при каждом старте, ломая всем настроенным клиентам tg://-ссылку. Добавлен --secret-file. - README обещал Rust 1.75+, тогда как Cargo.toml требует 1.88 и CI это проверяет. Это и есть первопричина #3. Политика доступа: прямые не-Telegram соединения разрешены только на loopback, на любом сетевом адресе нужен явный --allow-direct. Правило из ISSUE_AUDIT о том, что LAN не должен становиться открытым SOCKS5, теперь выражено в типе ListenConfig и покрыто тестами. Тесты: 46 в библиотеке + 12 в CLI. Появился сквозной тест туннеля против мок-релея, который реализует сторону Telegram по obfuscated2 — проверяется не внутренняя консистентность, а что реле получает ровно тот открытый текст, который отправил клиент, и обратно. Плюс расписание backoff, фолбэк при всех маршрутах в cooldown, валидация Worker-доменов, отказы SOCKS5, устойчивость секрета к перезапуску и корректная остановка по SIGTERM. Документация: секция CLI в README с юнитом systemd и Dockerfile. Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com> |