From c788c861847551227334187007f3f6592031231b Mon Sep 17 00:00:00 2001 From: Dushnilin Date: Sat, 22 Aug 2026 10:07:40 +0300 Subject: [PATCH] =?UTF-8?q?fix(warp):=20=D0=BF=D1=80=D0=B5=D1=84=D0=BB?= =?UTF-8?q?=D0=B0=D0=B9=D1=82-=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA?= =?UTF-8?q?=D0=B0=20Cloudflare=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20XH?= =?UTF-8?q?R=20timeout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit На сетях, где api.cloudflareclient.com недоступен (типичный случай из issue #39), генератор сжигал весь временной бюджет - фронтенд умирал по XHR timeout через 30 секунд, не показывая причины. - префлайт: одна проба (4с connect / 6с max) напрямую и через доступный транспорт; если Cloudflare недостижим - мгновенная понятная ошибка «Cloudflare API недоступен...» вместо минутного зависания - бюджеты ужаты до 20с на call_api и 20с на всю регистрацию, чтобы худший случай укладывался в XHR-таймаут LuCI - все ошибочные выходы теперь печатают JSON-конверт и exit(0): фронтенд парсит data.message и показывает настоящую причину (ненулевой код заставлял его писать безликое Unknown error) --- .../files/usr/lib/service/warp_generator.uc | 30 +++++++++++--- tests/warp_preflight.sh | 39 +++++++++++++++++++ 2 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 tests/warp_preflight.sh diff --git a/tachyon/files/usr/lib/service/warp_generator.uc b/tachyon/files/usr/lib/service/warp_generator.uc index 8c340aef..ca8d871f 100644 --- a/tachyon/files/usr/lib/service/warp_generator.uc +++ b/tachyon/files/usr/lib/service/warp_generator.uc @@ -99,7 +99,7 @@ function call_api(method, url, auth_header, body_file) { // Hard wall-clock budget: without it the full retry matrix (transports x // resolve IPs x fallbacks) can block for minutes and trip the LuCI XHR // timeout before any answer is produced. - let deadline = now_ms() + 40000; + let deadline = now_ms() + 20000; let transport_flags = []; if (proxy_interface != "") { @@ -114,6 +114,26 @@ function call_api(method, url, auth_header, body_file) { push(transport_flags, "-x socks5h://127.0.0.1:4534"); } + // Pre-flight: when the Cloudflare API is unreachable from this router + // (blocked ISP, dead proxy), the registration matrix below would burn its + // whole time budget and trip the LuCI XHR timeout before producing a + // readable answer. Probe once, fail fast with an actionable message. + let probe_cmd = "curl -s --connect-timeout 4 -m 6 -o /dev/null -w '%{http_code}' https://api.cloudflareclient.com/ 2>/dev/null"; + let probe = trim(exec_output(probe_cmd)); + if (probe == "" || probe == "000") { + let via_proxy = ""; + for (let tr in transport_flags) { + if (index(tr, "socks5h") >= 0 || index(tr, "--interface") >= 0) { + via_proxy = exec_output("curl -s --connect-timeout 4 -m 6 -o /dev/null -w '%{http_code}' " + tr + " https://api.cloudflareclient.com/ 2>/dev/null"); + break; + } + } + if (trim(via_proxy) == "" || trim(via_proxy) == "000") { + error_response("Cloudflare API недоступен с этого роутера (ни напрямую, ни через прокси). Генерация WARP невозможна — проверьте интернет или сгенерируйте конфиг через рабочую сеть."); + exit(0); + } + } + // 1. Try selected tunnel transport(s) for (let tr in transport_flags) { if (now_ms() > deadline) break; @@ -233,7 +253,7 @@ if (!private_key || !public_key) { if (!private_key || !public_key) { restore_original_server(); error_response("Failed to generate WireGuard keypair"); - exit(1); + exit(0); } let install_id = ""; @@ -255,7 +275,7 @@ let tmp_req_file = exec_output("mktemp 2>/dev/null") || "/tmp/warp_req.json"; if (fs.writefile(tmp_req_file, sprintf("%J", reg_request)) == null) { restore_original_server(); error_response("Failed to write temporary registration request file"); - exit(1); + exit(0); } let api_prefixes = [ @@ -278,7 +298,7 @@ let chosen_prefix = ""; let server_try_index = 0; // Overall budget for the whole registration attempt matrix: each call_api() // is bounded, but the prefix list is long enough to multiply into minutes. -let registration_deadline = now_ms() + 90000; +let registration_deadline = now_ms() + 20000; for (let prefix in api_prefixes) { if (now_ms() > registration_deadline) { @@ -333,7 +353,7 @@ if (!response_data) { if (warp_proxy_section == "") hint = " Провайдер может блокировать Cloudflare API на WAN. Выберите секцию с туннелем (WireGuard/AWG/VPN) или включите Mixed-прокси на прокси-секции в 'Расширенных настройках'."; error_response("Ошибка регистрации в Cloudflare WARP API: " + last_error + hint); - exit(1); + exit(0); } if (!response_data.warp_enabled) { diff --git a/tests/warp_preflight.sh b/tests/warp_preflight.sh new file mode 100644 index 00000000..7bc9fdfd --- /dev/null +++ b/tests/warp_preflight.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -eo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +WARP="$ROOT_DIR/tachyon/files/usr/lib/service/warp_generator.uc" + +fail() { + printf 'FAIL: %s\n' "$1" >&2 + exit 1 +} + +[ -f "$WARP" ] || fail "warp_generator.uc not found" + +# 1. Pre-flight probe: when the Cloudflare API is unreachable the generator +# must fail fast with an actionable message instead of burning its whole +# time budget and tripping the LuCI XHR timeout. +grep -Fq 'Cloudflare API недоступен с этого роутера' "$WARP" || + fail "generator must report an unreachable Cloudflare API explicitly" +grep -Fq "connect-timeout 4 -m 6" "$WARP" || + fail "pre-flight probe must be bounded to a few seconds" + +# 2. Time budgets must stay under the LuCI XHR timeout (~30 s): per-call 20s, +# whole registration 20s, so worst case fits inside one browser request. +grep -Fq 'let deadline = now_ms() + 20000;' "$WARP" || + fail "call_api budget must be 20s" +grep -Fq 'let registration_deadline = now_ms() + 20000;' "$WARP" || + fail "registration budget must be 20s" + +# 3. Error paths print the JSON envelope and exit 0: the frontend parses +# stdout (data.success/message) and shows the real reason; a nonzero exit +# makes it discard stdout for "Unknown error". +[ "$(grep -c 'exit(1);' "$WARP")" -eq 0 ] || + fail "error paths must exit(0) after printing the JSON error envelope" + +# 4. Clash API calls stay bounded. +grep -Fq 'curl -s -m 5 ' "$WARP" || + fail "local Clash API curls must carry -m 5" + +printf 'warp preflight checks passed\n'