mirror of
https://github.com/by-sonic/tglock.git
synced 2026-11-05 15:21:55 +03:00
`TransportEngine::connect` собирает подробный перечень попыток — какой адрес не
ответил, где истёк TLS, что вернул воркер, — и возвращает его в `Err`. Дальше
этот `Err` доходил до `serve`, где выбрасывался: `let _ = handle(...)`.
Увеличивался только счётчик.
Снаружи это выглядит как `туннелей 0 · сбоев 249 · падений маршрутов 395` без
единого слова о том, почему их ноль. Отличить «провайдер режет закреплённые
адреса» от «воркер отвечает отказом» нечем, хотя рядом есть журнал событий, в
который пишутся куда менее важные вещи.
Теперь причина попадает в журнал строкой вида:
Не поднялся туннель до DC2: 149.154.167.51 — не отвечает (таймаут TCP);
kws2.web.telegram.org — таймаут TLS/WebSocket
Дедупликация журнала делает её разовой: набор маршрутов у DC стабилен.
Там же вторая слепая зона. Домен воркера, не похожий на имя хоста, отбрасывался
молча: `https://name.workers.dev/` со схемой или слэшем не проходит
`valid_domain`, маршрут не появляется, и «воркер настроен» неотличимо от
«воркера нет». `set_worker_domains` теперь возвращает принятые и отвергнутые
по отдельности, отвергнутые называются вместе с причиной, принятые
подтверждаются.
Тексты отказов переведены на русский: их читает не разработчик, а человек,
который прислал скриншот и ждёт ответа.
Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com>
This commit is contained in:
+85
-2
@@ -217,7 +217,18 @@ impl Stats {
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
.map(str::to_owned)
|
||||
.collect::<Vec<_>>();
|
||||
self.transport.set_worker_domains(&domains);
|
||||
let result = self.transport.set_worker_domains(&domains);
|
||||
// Молчание здесь неотличимо от «воркер работает»: пока строка не
|
||||
// попадала в список маршрутов, об этом не сообщалось ничем, и человек
|
||||
// считал резервный маршрут настроенным (by-sonic/tglock#50).
|
||||
for rejected in &result.rejected {
|
||||
self.note(format!(
|
||||
"Cloudflare Worker «{rejected}» не похож на имя хоста — маршрут не добавлен. Нужно только имя, без https:// и без косой черты: example.workers.dev"
|
||||
));
|
||||
}
|
||||
for accepted in &result.accepted {
|
||||
self.note(format!("Cloudflare Worker в списке маршрутов: {accepted}"));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop(&self) {
|
||||
@@ -665,7 +676,17 @@ async fn ws_tunnel(
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
|
||||
let (ws, connected) = stats.transport.connect(dc, media).await?;
|
||||
let (ws, connected) = match stats.transport.connect(dc, media).await {
|
||||
Ok(connected) => connected,
|
||||
Err(error) => {
|
||||
// Единственное место, где известно, ПОЧЕМУ туннеля нет. Раньше
|
||||
// текст уходил в `Err` и там пропадал: оставался счётчик сбоев без
|
||||
// причины, и отличить «провайдер режет адреса» от «воркер отвечает
|
||||
// отказом» было нечем (by-sonic/tglock#50).
|
||||
stats.note(error.clone());
|
||||
return Err(error.into());
|
||||
}
|
||||
};
|
||||
let _tunnel = EstablishedTunnel::new(stats);
|
||||
stats.note_tunnel(dc, connected.route.kind.ui_code());
|
||||
|
||||
@@ -1473,6 +1494,68 @@ mod tests {
|
||||
let _ = server.await.unwrap();
|
||||
}
|
||||
|
||||
/// Диагностика обязана называть причину, а не только считать сбои.
|
||||
///
|
||||
/// При `туннелей 0` счётчик сбоев говорит, что не получилось, и молчит о
|
||||
/// том, почему. Текст ошибки собирался и выбрасывался, и разобрать
|
||||
/// «провайдер режет адреса» против «воркер отвечает отказом» было нечем
|
||||
/// (by-sonic/tglock#50).
|
||||
#[tokio::test]
|
||||
async fn a_cascade_that_failed_says_why_in_the_log() {
|
||||
let dead = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let dead_port = dead.local_addr().unwrap().port();
|
||||
drop(dead);
|
||||
|
||||
let stats = Stats::new();
|
||||
stats.transport.force_local_route(dead_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("сбой засчитан", || {
|
||||
stats.ws_failures.load(Ordering::Relaxed) > 0
|
||||
})
|
||||
.await;
|
||||
|
||||
let events = stats.drain_events();
|
||||
let named = events
|
||||
.iter()
|
||||
.find(|event| event.contains("Не поднялся туннель до DC2"))
|
||||
.unwrap_or_else(|| panic!("причина отказа не попала в журнал: {events:?}"));
|
||||
assert!(
|
||||
named.contains("127.0.0.1"),
|
||||
"в журнале должен быть назван адрес, до которого не дошли: {named}"
|
||||
);
|
||||
|
||||
stats.stop();
|
||||
let _ = server.await.unwrap();
|
||||
}
|
||||
|
||||
/// Строка, не похожая на имя хоста, отбрасывалась молча, и «воркер
|
||||
/// настроен» ничем не отличалось от «воркера нет» (by-sonic/tglock#50).
|
||||
#[test]
|
||||
fn a_worker_domain_is_confirmed_or_named_as_rejected() {
|
||||
let stats = Stats::new();
|
||||
stats.set_worker_domain("https://mine.workers.dev/, spare.workers.dev");
|
||||
let events = stats.drain_events();
|
||||
|
||||
assert!(
|
||||
events
|
||||
.iter()
|
||||
.any(|event| event.contains("https://mine.workers.dev/")
|
||||
&& event.contains("не похож на имя хоста")),
|
||||
"отвергнутый домен должен быть назван вместе с причиной: {events:?}"
|
||||
);
|
||||
assert!(
|
||||
events
|
||||
.iter()
|
||||
.any(|event| event.contains("в списке маршрутов: spare.workers.dev")),
|
||||
"принятый домен нужно подтвердить, иначе проверить нечем: {events:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires live Telegram network access"]
|
||||
async fn accepts_mtproto_and_builds_live_media_tunnel() {
|
||||
|
||||
Reference in New Issue
Block a user