fix(warp): timeout при прокси, четная длина hex для awg_i1..i5, исправление выбора секции прокси (#43)

This commit is contained in:
Dushnilin
2026-08-27 13:06:31 +03:00
parent 627941c2df
commit 7ddb7f6d4d
5 changed files with 180 additions and 89 deletions
@@ -5,12 +5,20 @@ import { TACHYON_UCI_PACKAGE } from '../../../../constants';
// ─── Types ───────────────────────────────────────────────────────────────────
export interface AdvSectionOption {
id: string;
label: string;
action: string;
iface?: string;
}
interface AdvancedSettingsState {
watchdogRunning: boolean;
watchdogLoading: boolean;
smartDetectEnabled: boolean;
smartDetectSections: string[];
allSectionNames: string[];
sectionOptions: AdvSectionOption[];
deviceIpsPerSection: Record<string, string[]>;
dnsTurboCache: boolean;
agentApiToken: string;
@@ -50,6 +58,7 @@ let _state: AdvancedSettingsState = {
smartDetectEnabled: false,
smartDetectSections: [],
allSectionNames: [],
sectionOptions: [],
deviceIpsPerSection: {},
dnsTurboCache: false,
agentApiToken: '',
@@ -119,6 +128,21 @@ export async function loadAdvancedSettingsState() {
);
const allSectionNames = ruleSections.map((s) => s['.name'] as string);
const sectionOptions: AdvSectionOption[] = sections
.filter((s) => s['.type'] === 'section' && s.enabled !== '0')
.map((s) => {
const rawSec = s as unknown as Record<string, unknown>;
return {
id: s['.name'] as string,
label: (s.label as string) || (s['.name'] as string),
action: (s.action as string) || 'section',
iface:
(rawSec.section_interface as string) ||
(rawSec.interface as string) ||
undefined,
};
});
const deviceIpsPerSection: Record<string, string[]> = {};
for (const s of ruleSections) {
const name = s['.name'] as string;
@@ -144,6 +168,7 @@ export async function loadAdvancedSettingsState() {
? smartDetectSections
: allSectionNames.slice(0, 1),
allSectionNames,
sectionOptions,
deviceIpsPerSection,
dnsTurboCache: settingsSec?.dns_turbo_cache === '1',
agentApiToken:
@@ -780,24 +805,60 @@ function renderAiWatchdogSection(state: AdvancedSettingsState) {
]),
E('div', { class: 'tachyon_adv__row' }, [
E('label', { class: 'tachyon_adv__label' }, _('WARP Proxy Section')),
E('input', {
type: 'text',
class: 'cbi-input-text',
value: state.warpProxySection,
placeholder: _('default (service mixed proxy)'),
onchange: (e: Event) => {
_state = {
..._state,
warpProxySection: (e.target as HTMLInputElement).value,
};
E(
'select',
{
class: 'cbi-input-select',
onchange: (e: Event) => {
_state = {
..._state,
warpProxySection: (e.target as HTMLSelectElement).value,
};
},
},
}),
[
E(
'option',
{
value: '',
selected: !state.warpProxySection,
},
_('Default (Direct WAN / Default mixed proxy)'),
),
...state.sectionOptions.map((sec) => {
const desc = sec.iface
? ` (${sec.action}: ${sec.iface})`
: ` (${sec.action})`;
return E(
'option',
{
value: sec.id,
selected: state.warpProxySection === sec.id,
},
`${sec.label}${desc}`,
);
}),
...(state.warpProxySection &&
!state.sectionOptions.some((s) => s.id === state.warpProxySection)
? [
E(
'option',
{
value: state.warpProxySection,
selected: true,
},
`${state.warpProxySection} (${_('Custom interface/section')})`,
),
]
: []),
],
),
]),
E(
'div',
{ class: 'tachyon_adv__hint' },
_(
'Section name for WARP registration proxy. Leave empty to use the default service mixed proxy.',
'Section or tunnel interface used for Cloudflare WARP API registration. Supports Connection, WireGuard, AmneziaWG, and Interface sections. Leave empty to use direct WAN / default mixed proxy.',
),
),
state.enableAiDoctor
@@ -1641,12 +1641,16 @@ function createSettingsContent(section, capabilities) {
};
o.load = function (section_id) {
const sections = this.map?.data?.state?.values?.[UCI_PACKAGE] ?? {};
this.keylist = [""];
this.vallist = ["", _("Default (Direct WAN / Default mixed proxy)")];
this.keylist = [];
this.vallist = [];
this.value("", _("Default (Direct WAN / Default mixed proxy)"));
for (const secName in sections) {
const sec = sections[secName];
if (sec[".type"] === "section" && sec.enabled !== "0") {
this.value(secName, sec.label || secName);
const action = sec.action || "section";
const iface = sec.section_interface || sec.interface || "";
const desc = iface ? ` (${action}: ${iface})` : ` (${action})`;
this.value(secName, `${sec.label || secName}${desc}`);
}
}
return this.cfgvalue(section_id);
+4 -1
View File
@@ -184,8 +184,11 @@ function awg_tag_chain(value) {
// Classic AmneziaWG form: plain hex (with optional 0x prefix).
let hex = lc(value);
hex = replace(hex, /^0x/, "");
if (match(hex, /^[0-9a-f]+$/) && length(hex) % 2 == 0)
if (match(hex, /^[0-9a-f]+$/)) {
if (length(hex) % 2 != 0)
hex += "0";
return "<b 0x" + hex + ">";
}
// Unsupported shape: emit nothing rather than a value that would be
// silently dropped at runtime; validation reports it.
+75 -66
View File
@@ -34,10 +34,14 @@ function get_clash_base_url() {
return "http://" + host;
}
// Convert <b 0x...> ucode binary literals to plain hex string
// Convert <b 0x...> ucode binary literals to plain hex string with guaranteed even length
function to_hex(s) {
s = replace(as_string(s), "<b 0x", "");
s = replace(s, ">", "");
s = replace(as_string(s), /<b 0x/g, "");
s = replace(s, />/g, "");
s = replace(s, /[ \t\r\n]/g, "");
if (s != "" && length(s) % 2 != 0) {
s += "0";
}
return s;
}
@@ -48,20 +52,25 @@ let proxy_port = "";
let proxy_interface = "";
if (warp_proxy_section != "") {
let section_action = uci.get(CONFIG_NAME, warp_proxy_section, "action") || "";
let section_iface = uci.get(CONFIG_NAME, warp_proxy_section, "section_interface") ||
uci.get(CONFIG_NAME, warp_proxy_section, "interface") || "";
let section_port = uci.get(CONFIG_NAME, warp_proxy_section, "mixed_proxy_port") || "";
if (section_iface != "") {
proxy_interface = section_iface;
} else if (section_action == "interface") {
if (uci.get(CONFIG_NAME, warp_proxy_section)) {
let section_action = uci.get(CONFIG_NAME, warp_proxy_section, "action") || "";
let section_iface = uci.get(CONFIG_NAME, warp_proxy_section, "section_interface") ||
uci.get(CONFIG_NAME, warp_proxy_section, "interface") ||
uci.get(CONFIG_NAME, warp_proxy_section, "device") || "";
let section_port = uci.get(CONFIG_NAME, warp_proxy_section, "mixed_proxy_port") || "";
if (section_iface != "") {
proxy_interface = section_iface;
} else if (section_action == "interface") {
proxy_interface = warp_proxy_section;
}
if (section_port != "" && match(section_port, /^[0-9]+$/) != null) {
proxy_port = section_port;
}
} else if (fs.stat("/sys/class/net/" + warp_proxy_section) != null) {
proxy_interface = warp_proxy_section;
}
if (section_port != "" && match(section_port, /^[0-9]+$/) != null) {
proxy_port = section_port;
}
}
function error_response(message) {
@@ -118,50 +127,51 @@ function tcp_port_listening(port) {
function call_api(method, url, auth_header, body_file) {
let resolve_ips = [ "162.159.192.1", "162.159.193.1", "162.159.195.1", "162.159.204.1", "188.114.96.1", "188.114.97.1", "188.114.98.1", "188.114.99.1" ];
let last_res = "";
// 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() + 20000;
// Hard wall-clock budget for multi-step registration
let deadline = now_ms() + 60000;
let lan_ip = trim(uci.get("network", "lan", "ipaddr") || "192.168.1.1");
let candidate_ips = [ "127.0.0.1" ];
if (lan_ip != "" && !contains(candidate_ips, lan_ip)) {
push(candidate_ips, lan_ip);
}
if (!contains(candidate_ips, "192.168.1.1")) {
push(candidate_ips, "192.168.1.1");
}
let transport_flags = [];
if (proxy_interface != "") {
push(transport_flags, "--interface " + proxy_interface);
}
if (proxy_port != "") {
push(transport_flags, "-x socks5h://127.0.0.1:" + proxy_port);
for (let ip in candidate_ips) {
let tr = "-x socks5h://" + ip + ":" + proxy_port;
if (!contains(transport_flags, tr)) push(transport_flags, tr);
}
}
// Liveness of the default service mixed proxy: a LOCAL check only.
// A full fetch through the tunnel would conflate "proxy dead" with
// "slow/blocked exit" and silently drop a working transport.
let fallback_port = as_string(common.get_mixed_port());
if (tcp_port_listening(int(fallback_port)) && !contains(transport_flags, "-x socks5h://127.0.0.1:" + fallback_port)) {
push(transport_flags, "-x socks5h://127.0.0.1:" + fallback_port);
if (fallback_port != "" && tcp_port_listening(int(fallback_port))) {
for (let ip in candidate_ips) {
let tr = "-x socks5h://" + ip + ":" + fallback_port;
if (!contains(transport_flags, tr)) push(transport_flags, tr);
}
}
// 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.
//
// Direct probes MUST use the same --resolve trick as the real calls
// below: ISP DNS poisoning of api.cloudflareclient.com would otherwise
// report the API as unreachable while registration itself still works.
let resolve_ips_probe = [ "162.159.192.1", "162.159.193.1", "162.159.195.1", "162.159.204.1", "188.114.96.1", "188.114.97.1", "188.114.98.1", "188.114.99.1" ];
// Pre-flight: probe proxies and tunnel interfaces FIRST, then direct WAN as fallback
let api_reachable = false;
for (let ip in resolve_ips_probe) {
let probe = trim(exec_output("curl -s --connect-timeout 3 -m 5 -o /dev/null -w '%{http_code}' --resolve api.cloudflareclient.com:443:" + ip + " https://api.cloudflareclient.com/ 2>/dev/null"));
for (let tr in transport_flags) {
let probe = trim(exec_output("curl -s --connect-timeout 3 -m 4 -o /dev/null -w '%{http_code}' " + tr + " https://api.cloudflareclient.com/ 2>/dev/null"));
if (probe != "" && probe != "000") {
api_reachable = true;
break;
}
}
if (!api_reachable) {
for (let tr in transport_flags) {
if (index(tr, "socks5h") < 0 && index(tr, "--interface") < 0)
continue;
// socks5h resolves on the proxy side, so no --resolve here.
let via_proxy = trim(exec_output("curl -s --connect-timeout 4 -m 6 -o /dev/null -w '%{http_code}' " + tr + " https://api.cloudflareclient.com/ 2>/dev/null"));
if (via_proxy != "" && via_proxy != "000") {
let resolve_ips_probe = [ "162.159.192.1", "188.114.96.1" ];
for (let ip in resolve_ips_probe) {
let probe = trim(exec_output("curl -s --connect-timeout 3 -m 4 -o /dev/null -w '%{http_code}' --resolve api.cloudflareclient.com:443:" + ip + " https://api.cloudflareclient.com/ 2>/dev/null"));
if (probe != "" && probe != "000") {
api_reachable = true;
break;
}
@@ -172,15 +182,15 @@ function call_api(method, url, auth_header, body_file) {
exit(0);
}
// 1. Try selected tunnel transport(s)
let auth_str = auth_header != "" ? "-H 'Authorization: Bearer " + auth_header + "' " : "";
let data_str = body_file != "" ? "-d @" + body_file + " " : "";
// 1. Try selected tunnel / proxy transport(s) FIRST
for (let tr in transport_flags) {
if (now_ms() > deadline) break;
// A. With --resolve to Cloudflare IPs
for (let ip in resolve_ips) {
if (now_ms() > deadline) break;
let auth_str = auth_header != "" ? "-H 'Authorization: Bearer " + auth_header + "' " : "";
let data_str = body_file != "" ? "-d @" + body_file + " " : "";
let cmd = "curl -s --connect-timeout 3 --max-time 7 " + tr + " --resolve api.cloudflareclient.com:443:" + ip + " -X " + method + " -H 'Content-Type: application/json' -H 'User-Agent: okhttp/3.12.1' " + auth_str + data_str + url;
if (index(tr, "socks5h") >= 0) {
// SOCKS remote DNS resolution: standard and fast
let cmd = "curl -s --connect-timeout 4 --max-time 8 " + tr + " -X " + method + " -H 'Content-Type: application/json' -H 'User-Agent: okhttp/3.12.1' " + auth_str + data_str + url;
let res = exec_output(cmd);
if (res != "") {
try {
@@ -191,20 +201,21 @@ function call_api(method, url, auth_header, body_file) {
last_res = res;
}
}
}
if (now_ms() > deadline) break;
// B. Directly via DNS on tunnel
let auth_str = auth_header != "" ? "-H 'Authorization: Bearer " + auth_header + "' " : "";
let data_str = body_file != "" ? "-d @" + body_file + " " : "";
let cmd = "curl -s --connect-timeout 3 --max-time 7 " + tr + " -X " + method + " -H 'Content-Type: application/json' -H 'User-Agent: okhttp/3.12.1' " + auth_str + data_str + url;
let res = exec_output(cmd);
if (res != "") {
try {
let parsed = json(res);
if (parsed) return parsed;
}
catch (e) {
last_res = res;
} else {
// Tunnel interface: probe with resolved Cloudflare IPs
for (let ip in resolve_ips) {
if (now_ms() > deadline) break;
let cmd = "curl -s --connect-timeout 3 --max-time 7 " + tr + " --resolve api.cloudflareclient.com:443:" + ip + " -X " + method + " -H 'Content-Type: application/json' -H 'User-Agent: okhttp/3.12.1' " + auth_str + data_str + url;
let res = exec_output(cmd);
if (res != "") {
try {
let parsed = json(res);
if (parsed) return parsed;
}
catch (e) {
last_res = res;
}
}
}
}
}
@@ -216,8 +227,6 @@ function call_api(method, url, auth_header, body_file) {
// 2. Direct WAN fallback if no tunnel transport worked
for (let ip in resolve_ips) {
if (now_ms() > deadline) break;
let auth_str = auth_header != "" ? "-H 'Authorization: Bearer " + auth_header + "' " : "";
let data_str = body_file != "" ? "-d @" + body_file + " " : "";
let cmd = "curl -s --connect-timeout 3 --max-time 6 --resolve api.cloudflareclient.com:443:" + ip + " -X " + method + " -H 'Content-Type: application/json' -H 'User-Agent: okhttp/3.12.1' " + auth_str + data_str + url;
let res = exec_output(cmd);
if (res != "") {
@@ -336,7 +345,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() + 20000;
let registration_deadline = now_ms() + 60000;
for (let prefix in api_prefixes) {
if (now_ms() > registration_deadline) {
@@ -468,7 +477,7 @@ let random_port = warp_ports[time() % length(warp_ports)];
let warp_cps_payloads = [
"<b 0xce000000010897a297ecc34cd6dd000044d0ec2e2e1ea2991f467ace4222129b5a098823784694b4897b9986ae0b7280135fa85e196d9ad980b150122129ce2a9379531b0fd3e871ca5fdb883c369832f730e272d7b8b74f393f9f0fa43f11e510ecb2219a52984410c204cf875585340c62238e14ad04dff382f2c200e0ee22fe743b9c6b8b043121c5710ec289f471c91ee414fca8b8be8419ae8ce7ffc53837f6ade262891895f3f4cecd31bc93ac5599e18e4f01b472362b8056c3172b513051f8322d1062997ef4a383b01706598d08d48c221d30e74c7ce000cdad36b706b1bf9b0607c32ec4b3203a4ee21ab64df336212b9758280803fcab14933b0e7ee1e04a7becce3e2633f4852585c567894a5f9efe9706a151b615856647e8b7dba69ab357b3982f554549bef9256111b2d67afde0b496f16962d4957ff654232aa9e845b61463908309cfd9de0a6abf5f425f577d7e5f6440652aa8da5f73588e82e9470f3b21b27b28c649506ae1a7f5f15b876f56abc4615f49911549b9bb39dd804fde182bd2dcec0c33bad9b138ca07d4a4a1650a2c2686acea05727e2a78962a840ae428f55627516e73c83dd8893b02358e81b524b4d99fda6df52b3a8d7a5291326e7ac9d773c5b43b8444554ef5aea104a738ed650aa979674bbed38da58ac29d87c29d387d80b526065baeb073ce65f075ccb56e47533aef357dceaa8293a523c5f6f790be90e4731123d3c6152a70576e90b4ab5bc5ead01576c68ab633ff7d36dcde2a0b2c68897e1acfc4d6483aaaeb635dd63c96b2b6a7a2bfe042f6aed82e5363aa850aace12ee3b1a93f30d8ab9537df483152a5527faca21efc9981b304f11fc95336f5b9637b174c5a0659e2b22e159a9fed4b8e93047371175b1d6d9cc8ab745f3b2281537d1c75fb9451871864efa5d184c38c185fd203de206751b92620f7c369e031d2041e152040920ac2c5ab5340bfc9d0561176abf10a147287ea90758575ac6a9f5ac9f390d0d5b23ee12af583383d994e22c0cf42383834bcd3ada1b3825a0664d8f3fb678261d57601ddf94a8a68a7c273a18c08aa99c7ad8c6c42eab67718843597ec9930457359dfdfbce024afc2dcf9348579a57d8d3490b2fa99f278f1c37d87dad9b221acd575192ffae1784f8e60ec7cee4068b6b988f0433d96d6a1b1865f4e155e9fe020279f434f3bf1bd117b717b92f6cd1cc9bea7d45978bcc3f24bda631a36910110a6ec06da35f8966c9279d130347594f13e9e07514fa370754d1424c0a1545c5070ef9fb2acd14233e8a50bfc5978b5bdf8bc1714731f798d21e2004117c61f2989dd44f0cf027b27d4019e81ed4b5c31db347c4a3a4d85048d7093cf16753d7b0d15e078f5c7a5205dc2f87e330a1f716738dce1c6180e9d02869b5546f1c4d2748f8c90d9693cba4e0079297d22fd61402dea32ff0eb69ebd65a5d0b687d87e3a8b2c42b648aa723c7c7daf37abcc4bb85caea2ee8f55bec20e913b3324ab8f5c3304f820d42ad1b9f2ffc1a3af9927136b4419e1e579ab4c2ae3c776d293d397d575df181e6cae0a4ada5d67ecea171cca3288d57c7bbdaee3befe745fb7d634f70386d873b90c4d6c6596bb65af68f9e5121e67ebf0d89d3c909ceedfb32ce9575a7758ff080724e1ab5d5f43074ecb53a479af21ed03d7b6899c36631c0166f9d47e5e1d4528a5d3d3f744029c4b1c190cbfbad06f5f83f7ad0429fa9a2719c56ffe3783460e166de2d8>",
"<b 0xc7000000010809a1ed4edbbe7615000044d017a61a0d774f04290f119e701ef0035df2b0ed571b0b575e6a07246b856eb6ec036fef07f1e07b861251ad737abeb67e64be714c1dcd865312b1b6c35c089c997aeb5c18f808696fe97289513945d84ca846467603e94e44224877f2c1d3261e4ac18740be4bd064369c94fc08978d99b54bf615250998639010c1284248e1d73004b81fcb20b559d8a17eced7eab3964b5b88ca7a3b8579fc8c1c934189e77143b4ac434138114b1048651b56545b87acbef0952763538f3ddeb37cfc6d58b4881c3b719d7ff78f6ee1324a2914a32381c05a64c700466d280be007253bb030d179c4f1b3dc221e1974e2ee6d6e2b9e8d709159b5ef22e1783dbba845c20ca1c83b066c73835920ad70b806df0aee0351e3fc9ab1e42e8b2a30fe235ff0612eee19744949cecee0463b76514ad90c1f7ceaa557c18586ab561d49482e73c85d0143785da14a441bf82f78783b61cccd44aecb1947516e79b5ca5a6b3a8aed6040fae0eeabdc55a88dc19ade832d99fca90c7a629cacc07192d7e47e3c6a271b95b0ea3392562a06a1cab79f40ea92916ebee197b7b5f14b251824e1ed20ff2ca80b1f03a43e45157589bc61b978e97851025b3b7ccc17d291e1cb60fe48a5c26829dce11dd23c2e73265a9ebf8617c985e4fee4681e863f990061f4dea465a7d2524bd0edcf4b48d4b8f25fc359b15babd2637284a4774077dca60091f1a781cfee1bef9713dd5943a579d7470bc5970542fbb27fdf77880a8d8751b1f642c7a3f019a05ab94bf63d3525ef34e9290b5c8d477f2714e6d6e3e4d35c1983f5e16fda57fcdf071b513f8f088dbe8d5a97577d17a5383a496c3f313adfdd47c962bbaebd6aa13b46439eb742622c29ca067db0ec1853064c3cbbffe0a215a19fce47d49703ed58ebbd89721172d256d1cf30188106fb2f863186511401fad54d087aa2fb3d1b85768db386bd7102e8060ac157bac011acdcdae2799b9aee1467c3424013455bd028fcaacdc3c77d28ea199967d617ea7d0d0815f3cc407934a76d1293dccba210d1709a13e5dd67c9ba47cd113f5bdd740358eff13164159fd09bc2f7ec6cfa64d9df7e2e2f88706b0ff3a92ccf6f078456cfe0bdd89292cfe2680badc1eac9f7d36efe8eb6912c7b164508d13e6c0911c15f73c233cbe4fc70ff2ade1e1be4bbb738e0939159e2078a9438f05b756a003371f4861481c38f1cdd2d7b06deb62869e9fe79a8abaa920646fa2e8fa28f0d80c136376c7b56046bae4c05c0cdf64efb8c47bbfc5a1a4c0b045061ef0d71618e0d206a1d7f245fd5c03191b152673ba8dff8e1b8de7c50234a93cba91e3888adb228cc02beded4b1c0946797d3ef02dec2edb6ad0ac21f89f4be364c317da7c22440e9f358d512203f4b7ab20388af68b8915d0152db2c8a0687bfaea870f7529bb92a22b35bd79bc6d490591406346ecd78342ee3563c4883a8251679691c2d4e963397e24653520795511b018915374c954bddb940a9d7a16d1c8bd798fc7dbfb0599a7074e13f87e14efa8d511bb2579ec029b1bda18fe971b30fbe19e986ff2686a69bf3f1bb929de93ae70345ebca998b11e0a2b41890cba628d8f6e7c4e94790735e5299b4ff07cd3080f7d53c9cbe1911d2cd5925b3213e033c272506a87886cf761a283a779564d3241e3c28f632e166b5d756e1786ce077614c4444e3f2aed5decb3613b925ea3e558c21d4faf8ba54edd0f3a5d4>",
"<b 0xc10000000114367096bb0fb3f58f3a3fb8aaacd61d63a1c8a40e14f7374b8a62dccba6431716c3abf6f5afbcfb39bd008000047c32e268567c652e6f4db58bff759bc8c5aaca183b87cb4d22938fe7d8dca22a679a79e4d9ee62e4bbb3a380dd78d4e8e48f26b38a1d42d76b371a5a9a0444827a69d1ab5872a85749f65a4104e931740b4dc1e2dd77733fc7fac4f93011cd622f2bb47e85f71992e2d585f8dc765a7a12ddeb879746a267393ad023d267c4bd79f258703e27345155268bd3cc0506ebd72e2e3c6b5b0f005299cd94b67ddabe30389c4f9b5c2d512dcc298c14f14e9b7f931e1dc397926c31fbb7cebfc668349c218672501031ecce151d4cb03c4c660b6c6fe7754e75446cd7de09a8c81030c5f6fb377203f551864f3d83e27de7b86499736cbbb549b2f37f436db1cae0a4ea39930f0534aacdd1e3534bc87877e2afabe959ced261f228d6362e6fd277c88c312d966c8b9f67e4a92e757773db0b0862fb8108d1d8fa262a40a1b4171961f0704c8ba314da2482ac8ed9bd28d4b50f7432d89fd800c25a50c5e2f5c0710544fef5273401116aa0572366d8e49ad758fcb29e6a92912e644dbe227c247cb3417eabfab2db16796b2fba420de3b1dc94e8361f1f324a331ddaf1e626553138860757fd0bf687566108b77b70fb9f8f8962eca599c4a70ed373666961a8cb506b96756d9e28b94122b20f16b54f118c0e603ce0b831efea614ad836df6cf9affbdd09596412547496967da758cec9080295d853b0861670b71d9abde0d562b1a6de82782a5b0c14d297f27283a895abc889a5f6703f0e6eb95f67b2da45f150d0d8ab805612d570c2d5cb6997ac3a7756226c2f5c8982ffbd480c5004b0660a3c9468945efde90864019a2b519458724b55d766e16b0da25c0557c01f3c11ddeb024b62e303640e17fdd57dedb3aeb4a2c1b7c93059f9c1d7118d77caac1cd0f6556e46cbc991c1bb16970273dea833d01e5090d061a0c6d25af2415cd2878af97f6d0e7f1f936247b394ecb9bd484da6be936dee9b0b92dc90101a1b4295e97a9772f2263eb09431995aa173df4ca2abd687d87706f0f93eaa5e13cbe3b574fa3cfe94502ace25265778da6960d561381769c24e0cbd7aac73c16f95ae74ff7ec38124f7c722b9cb151d4b6841343f29be8f35145e1b27021056820fed77003df8554b4155716c8cf6049ef5e318481460a8ce3be7c7bfac695255be84dc491c19e9dedc449dd3471728cd2a3ee51324ccb3eef121e3e08f8e18f0006ea8957371d9f2f739f0b89e4db11e5c6430ada61572e589519fbad4498b460ce6e4407fc2d8f2dd4293a50a0cb8fcaaf35cd9a8cc097e3603fbfa08d9036f52b3e7fcce11b83ad28a4ac12dba0395a0cc871cefd1a2856fffb3f28d82ce35cf80579974778bab13d9b3578d8c75a2d196087a2cd439aff2bb33f2db24ac175fff4ed91d36a4cdbfaf3f83074f03894ea40f17034629890da3efdbb41141b38368ab532209b69f057ddc559c19bc8ae62bf3fd564c9a35d9a83d14a95834a92bae6d9a29ae5e8ece07910d16433e4c6230c9bd7d68b47de0de9843988af6cdc88b5301820443bd4d0537778bf6b4c1dd067fcf14b81015f2a67c7f2a28f9cb7e0684d3cb4b1c24d9b343122a086611b489532f1c3a26779da1706c6759d96d8ab>"
"<b 0xc10000000114367096bb0fb3f58f3a3fb8aaacd61d63a1c8a40e14f7374b8a62dccba6431716c3abf6f5afbcfb39bd008000047c32e268567c652e6f4db58bff759bc8c5aaca183b87cb4d22938fe7d8dca22a679a79e4d9ee62e4bbb3a380dd78d4e8e48f26b38a1d42d76b371a5a9a0444827a69d1ab5872a85749f65a4104e931740b4dc1e2dd77733fc7fac4f93011cd622f2bb47e85f71992e2d585f8dc765a7a12ddeb879746a267393ad023d267c4bd79f258703e27345155268bd3cc0506ebd72e2e3c6b5b0f005299cd94b67ddabe30389c4f9b5c2d512dcc298c14f14e9b7f931e1dc397926c31fbb7cebfc668349c218672501031ecce151d4cb03c4c660b6c6fe7754e75446cd7de09a8c81030c5f6fb377203f551864f3d83e27de7b86499736cbbb549b2f37f436db1cae0a4ea39930f0534aacdd1e3534bc87877e2afabe959ced261f228d6362e6fd277c88c312d966c8b9f67e4a92e757773db0b0862fb8108d1d8fa262a40a1b4171961f0704c8ba314da2482ac8ed9bd28d4b50f7432d89fd800c25a50c5e2f5c0710544fef5273401116aa0572366d8e49ad758fcb29e6a92912e644dbe227c247cb3417eabfab2db16796b2fba420de3b1dc94e8361f1f324a331ddaf1e626553138860757fd0bf687566108b77b70fb9f8f8962eca599c4a70ed373666961a8cb506b96756d9e28b94122b20f16b54f118c0e603ce0b831efea614ad836df6cf9affbdd09596412547496967da758cec9080295d853b0861670b71d9abde0d562b1a6de82782a5b0c14d297f27283a895abc889a5f6703f0e6eb95f67b2da45f150d0d8ab805612d570c2d5cb6997ac3a7756226c2f5c8982ffbd480c5004b0660a3c9468945efde90864019a2b519458724b55d766e16b0da25c0557c01f3c11ddeb024b62e303640e17fdd57dedb3aeb4a2c1b7c93059f9c1d7118d77caac1cd0f6556e46cbc991c1bb16970273dea833d01e5090d061a0c6d25af2415cd2878af97f6d0e7f1f936247b394ecb9bd484da6be936dee9b0b92dc90101a1b4295e97a9772f2263eb09431995aa173df4ca2abd687d87706f0f93eaa5e13cbe3b574fa3cfe94502ace25265778da6960d561381769c24e0cbd7aac73c16f95ae74ff7ec38124f7c722b9cb151d4b6841343f29be8f35145e1b27021056820fed77003df8554b4155716c8cf6049ef5e318481460a8ce3be7c7bfac695255be84dc491c19e9dedc449dd3471728cd2a3ee51324ccb3eef121e3e08f8e18f0006ea8957371d9f2f739f0b89e4db11e5c6430ada61572e589519fbad4498b460ce6e4407fc2d8f2dd4293a50a0cb8fcaaf35cd9a8cc097e3603fbfa08d9036f52b3e7fcce11b83ad28a4ac12dba0395a0cc871cefd1a2856fffb3f28d82ce35cf80579974778bab13d9b3578d8c75a2d196087a2cd439aff2bb33f2db24ac175fff4ed91d36a4cdbfaf3f83074f03894ea40f17034629890da3efdbb41141b38368ab532209b69f057ddc559c19bc8ae62bf3fd564c9a35d9a83d14a95834a92bae6d9a29ae5e8ece07910d16433e4c6230c9bd7d68b47de0de9843988af6cdc88b5301820443bd4d0537778bf6b4c1dd067fcf14b81015f2a67c7f2a28f9cb7e0684d3cb4b1c24d9b343122a086611b489532f1c3a26779da1706c6759d96d8ab00>"
];
function generate_random_hex(len) {
+21 -7
View File
@@ -16,7 +16,7 @@ fail() {
# 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" ||
grep -Fq "connect-timeout 3 -m 4" "$WARP" ||
fail "pre-flight probe must be bounded to a few seconds"
# 1a. Direct pre-flight probes must use --resolve like the real calls:
@@ -33,12 +33,11 @@ grep -Fq 'tcp_port_listening(' "$WARP" ||
grep -Fq 'function tcp_port_listening' "$WARP" ||
fail "tcp_port_listening helper must exist"
# 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"
# 2. Time budgets: per-call 60s, whole registration 60s.
grep -Fq 'let deadline = now_ms() + 60000;' "$WARP" ||
fail "call_api budget must be 60s"
grep -Fq 'let registration_deadline = now_ms() + 60000;' "$WARP" ||
fail "registration budget must be 60s"
# 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
@@ -50,4 +49,19 @@ grep -Fq 'let registration_deadline = now_ms() + 20000;' "$WARP" ||
grep -Fq 'curl -s -m 5 ' "$WARP" ||
fail "local Clash API curls must carry -m 5"
# 5. AWG tag chain handles odd-length hex safely
ucode -L "$ROOT_DIR/tachyon/files/usr/lib" -e '
let common = require("core.common");
let res1 = common.awg_tag_chain("12345");
if (res1 != "<b 0x123450>") {
warn("awg_tag_chain should pad odd length hex: got " + res1 + "\n");
exit(1);
}
let res2 = common.awg_tag_chain("1234");
if (res2 != "<b 0x1234>") {
warn("awg_tag_chain should format even length hex: got " + res2 + "\n");
exit(1);
}
' || fail "awg_tag_chain odd-hex padding failed"
printf 'warp preflight checks passed\n'