mirror of
https://github.com/by-sonic/tglock.git
synced 2026-08-04 07:02:41 +03:00
docs(readme): убрать невыполнимые обещания, свести рекламу в один блок (#26)
fix(proxy): считать туннель только после успешного рукопожатия README обещал то, чего код не делает. Проверено по исходникам, исправлено: - «Голосовые/видеозвонки рвутся» стояло в списке «кому подойдёт», то есть подразумевалось, что TGLock их лечит. Не лечит: звонки по UDP, проксируется только TCP. То же обещание было в FAQ. Появился явный раздел «чего TGLock не делает» — звонки, всё кроме Telegram, Android и iOS. - «IP отобразится прямо в интерфейсе TGLock» в описании LAN-режима. Такого поля в интерфейсе нет: StatusSnapshot отдаёт только порт. Заменено на то, что есть — готовую tg://-ссылку и команды для поиска адреса руками. - «Кода ~350 строк» — в действительности 2872 строки Rust (из них ~1140 тесты) и ~380 строк TypeScript. - «DC ID — i32 в [60..64]» — на самом деле i16 в [60..62], отрицательное значение означает медиа-соединение. - Транспорт описывался как единственный маршрут через web.telegram.org. В 2.0 это каскад: закреплённые IP, дублёры kwsN-1, системный DNS и опциональный Cloudflare Worker, с cooldown на упавших. Указано, что системный DNS и hosts не изменяются, а SNI и Host остаются настоящими. - FAQ про macOS ссылался на файл tglock-macos-arm64, которого в релизах нет. - «Собирает бинарники для всех 4 платформ» — их три, плюс CLI. - Пустая колонка «Размер» в таблице загрузок заполнена реальными размерами. - FAQ про использование как обычного SOCKS5 не отражал, что на сетевом адресе не-Telegram запросы отклоняются. - FAQ про блокировку web.telegram.org обещал спасение, которого нет; теперь там сказано и про запас маршрутов, и про предел подхода. Реклама RoseVPN сведена в один блок сверху: удалены секция внизу, три вставки в FAQ и ссылка в подвале. Добавлен раздел «Как помочь» с тем, что прислать в баг-репорте, и списком известных ограничений, по которым issue открывать не нужно. Правка кода, без которой один из абзацев README был бы неправдой: счётчик Stats::ws увеличивался до WebSocket-рукопожатия, поэтому пока прокси перебирал маршруты по несколько секунд каждый, интерфейс уже показывал «Telegram на связи». Теперь счёт ведёт RAII-guard после успешного connect, и состояния «порт открыт», «идёт перебор» и «туннель есть» различимы. Тест a_tunnel_counts_only_after_the_handshake_succeeds держит это: молчащий listener, рукопожатие в полёте, ws и last_route обязаны остаться нулями. Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com>
This commit is contained in:
+65
-4
@@ -242,11 +242,9 @@ async fn handle_socks5(
|
||||
});
|
||||
|
||||
stats.last_dc.store(dc, Ordering::Relaxed);
|
||||
stats.ws.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
let r = ws_tunnel(s, dc, media, &init, None, stats).await;
|
||||
|
||||
stats.ws.fetch_sub(1, Ordering::Relaxed);
|
||||
if r.is_err() {
|
||||
stats.ws_failures.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
@@ -274,7 +272,6 @@ async fn handle_mtproto(
|
||||
.ok_or("invalid MTProto init or secret")?;
|
||||
|
||||
stats.last_dc.store(parsed.dc, Ordering::Relaxed);
|
||||
stats.ws.fetch_add(1, Ordering::Relaxed);
|
||||
let result = ws_tunnel(
|
||||
stream,
|
||||
parsed.dc,
|
||||
@@ -284,7 +281,6 @@ async fn handle_mtproto(
|
||||
stats,
|
||||
)
|
||||
.await;
|
||||
stats.ws.fetch_sub(1, Ordering::Relaxed);
|
||||
if result.is_err() {
|
||||
stats.ws_failures.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
@@ -401,6 +397,28 @@ fn dc_from_ip(ip: Ipv4Addr) -> Option<u16> {
|
||||
|
||||
// -- WebSocket tunnel -------------------------------------------------------
|
||||
|
||||
/// Keeps `Stats::ws` equal to the number of *established* tunnels.
|
||||
///
|
||||
/// Counting attempts instead would let the interface announce «Telegram на
|
||||
/// связи» while the WebSocket handshake is still failing over between routes,
|
||||
/// which takes seconds per route. Reporting a working tunnel that does not
|
||||
/// exist yet is the whole reason users saw «прокси подключён, а Telegram не
|
||||
/// работает».
|
||||
struct EstablishedTunnel<'a>(&'a Stats);
|
||||
|
||||
impl<'a> EstablishedTunnel<'a> {
|
||||
fn new(stats: &'a Stats) -> Self {
|
||||
stats.ws.fetch_add(1, Ordering::Relaxed);
|
||||
Self(stats)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EstablishedTunnel<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.0.ws.fetch_sub(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
async fn ws_tunnel(
|
||||
tcp: TcpStream,
|
||||
dc: u16,
|
||||
@@ -412,6 +430,7 @@ async fn ws_tunnel(
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
|
||||
let (mut ws, connected) = stats.transport.connect(dc, media).await?;
|
||||
let _tunnel = EstablishedTunnel::new(stats);
|
||||
stats
|
||||
.last_route
|
||||
.store(connected.route.kind.ui_code(), Ordering::Relaxed);
|
||||
@@ -900,6 +919,48 @@ mod tests {
|
||||
let _ = server.await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_tunnel_counts_only_after_the_handshake_succeeds() {
|
||||
// Accepts TCP and then stays silent, so the WebSocket handshake never
|
||||
// completes: the proxy is mid-attempt and no tunnel exists.
|
||||
let silent = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let relay_port = silent.local_addr().unwrap().port();
|
||||
let held = tokio::spawn(async move {
|
||||
let accepted = silent.accept().await;
|
||||
tokio::time::sleep(Duration::from_secs(30)).await;
|
||||
drop(accepted);
|
||||
});
|
||||
|
||||
let stats = Stats::new();
|
||||
stats.transport.force_local_route(relay_port);
|
||||
let (port, server) = start_proxy(stats.clone(), false).await;
|
||||
|
||||
let init = unambiguous_client_init(&stats.secret, 2);
|
||||
let mut client = TcpStream::connect(("127.0.0.1", port)).await.unwrap();
|
||||
client.write_all(&init).await.unwrap();
|
||||
|
||||
wait_until("the init to be parsed", || {
|
||||
stats.last_dc.load(Ordering::Relaxed) == 2
|
||||
})
|
||||
.await;
|
||||
tokio::time::sleep(Duration::from_millis(300)).await;
|
||||
|
||||
assert_eq!(
|
||||
stats.ws.load(Ordering::Relaxed),
|
||||
0,
|
||||
"a handshake still in flight must not be reported as a working tunnel"
|
||||
);
|
||||
assert_eq!(
|
||||
stats.last_route.load(Ordering::Relaxed),
|
||||
0,
|
||||
"no route may be announced before a tunnel is established"
|
||||
);
|
||||
|
||||
stats.stop();
|
||||
held.abort();
|
||||
let _ = server.await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn counts_a_failure_when_no_route_answers() {
|
||||
let dead = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
|
||||
Reference in New Issue
Block a user