fix(warp): префлайт-проверка Cloudflare вместо XHR timeout
На сетях, где 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)
This commit is contained in:
@@ -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
|
// 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
|
// resolve IPs x fallbacks) can block for minutes and trip the LuCI XHR
|
||||||
// timeout before any answer is produced.
|
// timeout before any answer is produced.
|
||||||
let deadline = now_ms() + 40000;
|
let deadline = now_ms() + 20000;
|
||||||
|
|
||||||
let transport_flags = [];
|
let transport_flags = [];
|
||||||
if (proxy_interface != "") {
|
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");
|
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)
|
// 1. Try selected tunnel transport(s)
|
||||||
for (let tr in transport_flags) {
|
for (let tr in transport_flags) {
|
||||||
if (now_ms() > deadline) break;
|
if (now_ms() > deadline) break;
|
||||||
@@ -233,7 +253,7 @@ if (!private_key || !public_key) {
|
|||||||
if (!private_key || !public_key) {
|
if (!private_key || !public_key) {
|
||||||
restore_original_server();
|
restore_original_server();
|
||||||
error_response("Failed to generate WireGuard keypair");
|
error_response("Failed to generate WireGuard keypair");
|
||||||
exit(1);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let install_id = "";
|
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) {
|
if (fs.writefile(tmp_req_file, sprintf("%J", reg_request)) == null) {
|
||||||
restore_original_server();
|
restore_original_server();
|
||||||
error_response("Failed to write temporary registration request file");
|
error_response("Failed to write temporary registration request file");
|
||||||
exit(1);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let api_prefixes = [
|
let api_prefixes = [
|
||||||
@@ -278,7 +298,7 @@ let chosen_prefix = "";
|
|||||||
let server_try_index = 0;
|
let server_try_index = 0;
|
||||||
// Overall budget for the whole registration attempt matrix: each call_api()
|
// Overall budget for the whole registration attempt matrix: each call_api()
|
||||||
// is bounded, but the prefix list is long enough to multiply into minutes.
|
// 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) {
|
for (let prefix in api_prefixes) {
|
||||||
if (now_ms() > registration_deadline) {
|
if (now_ms() > registration_deadline) {
|
||||||
@@ -333,7 +353,7 @@ if (!response_data) {
|
|||||||
if (warp_proxy_section == "")
|
if (warp_proxy_section == "")
|
||||||
hint = " Провайдер может блокировать Cloudflare API на WAN. Выберите секцию с туннелем (WireGuard/AWG/VPN) или включите Mixed-прокси на прокси-секции в 'Расширенных настройках'.";
|
hint = " Провайдер может блокировать Cloudflare API на WAN. Выберите секцию с туннелем (WireGuard/AWG/VPN) или включите Mixed-прокси на прокси-секции в 'Расширенных настройках'.";
|
||||||
error_response("Ошибка регистрации в Cloudflare WARP API: " + last_error + hint);
|
error_response("Ошибка регистрации в Cloudflare WARP API: " + last_error + hint);
|
||||||
exit(1);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response_data.warp_enabled) {
|
if (!response_data.warp_enabled) {
|
||||||
|
|||||||
@@ -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'
|
||||||
Reference in New Issue
Block a user