feat(zapret2): add high-performance strategy generator and tuner

This commit is contained in:
Dushnilin
2026-07-29 11:30:23 +03:00
parent c7ff142a48
commit 8b8c4b7df1
5 changed files with 562 additions and 0 deletions
+3
View File
@@ -68,6 +68,9 @@ export namespace Tachyon {
GET_ZAPRET_STATUS = 'get_zapret_status',
GET_ZAPRET2_STATUS = 'get_zapret2_status',
GET_BYEDPI_STATUS = 'get_byedpi_status',
ZAPRET2_TUNE = 'zapret2_tune',
ZAPRET2_TUNE_ASYNC = 'zapret2_tune_async',
ZAPRET2_TUNE_STATUS = 'zapret2_tune_status',
CLASH_API = 'clash_api',
ENABLE = 'enable',
DISABLE = 'disable',
+3
View File
@@ -215,6 +215,9 @@ function command_spec(command) {
validate_nfqws_strategy_json: [ "diagnostics/runtime.uc", "validate-nfqws-strategy-json", 1 ],
validate_nfqws2_strategy_json: [ "diagnostics/runtime.uc", "validate-nfqws2-strategy-json", 1 ],
validate_byedpi_strategy_json: [ "providers/byedpi/validator.uc", "validate-json", 1 ],
zapret2_tune: [ "providers/zapret2/tune.uc", "tune", 3 ],
zapret2_tune_async: [ "service/ui.uc", "zapret2-tune-async", 3 ],
zapret2_tune_status: [ "service/ui.uc", "zapret2-tune-status", 1 ],
generate_warp: [ "service/warp_generator.uc", "", 1 ],
package_prerm: [ "service/package.uc", "prerm", 1 ],
package_postinst: [ "service/package.uc", "postinst", 0 ],
@@ -0,0 +1,416 @@
#!/usr/bin/env ucode
let fs = require("fs");
let constants = require("core.constants");
let uci_core = require("core.uci");
let zapret2_common = require("providers.zapret2.common");
let zapret2_validator = require("providers.zapret2.validator");
const LIB_DIR = getenv("TACHYON_LIB") || "/usr/lib/tachyon";
const CONFIG_NAME = getenv("TACHYON_CONFIG_NAME") || constants.TACHYON_CONFIG_NAME || "tachyon";
const CACHE_FILE = "/tmp/tachyon_zapret2_tuning_cache.json";
function as_string(value) {
return value == null ? "" : "" + value;
}
function trim(value) {
value = as_string(value);
let start = 0;
let end = length(value);
while (start < end && match(substr(value, start, 1), /[ \t\r\n]/) != null)
start++;
while (end > start && match(substr(value, end - 1, 1), /[ \t\r\n]/) != null)
end--;
return substr(value, start, end - start);
}
function now_seconds() {
return time();
}
function write_json(value) {
print(sprintf("%J", value), "\n");
}
function read_json_file(path) {
path = as_string(path);
if (path == "" || fs.stat(path) == null)
return null;
let content = fs.readfile(path);
if (content == null || content == "")
return null;
try {
return json(content);
} catch (e) {
return null;
}
}
function write_json_file(path, data) {
path = as_string(path);
if (path == "")
return false;
let content = sprintf("%J", data);
let tmp_path = path + ".tmp." + now_seconds();
let file = fs.open(tmp_path, "w");
if (!file)
return false;
file.write(content);
file.close();
return fs.rename(tmp_path, path);
}
function shell_quote(value) {
return "'" + replace(as_string(value), /'/g, "'\\''") + "'";
}
function command_status(cmd) {
let status = int(system(cmd + " >/dev/null 2>&1"));
return status > 255 ? int(status / 256) : status;
}
function command_output(cmd) {
let pipe = fs.popen(cmd, "r");
if (!pipe)
return "";
let data = pipe.read("all");
pipe.close();
return as_string(data);
}
function get_section_domain(section_id) {
section_id = as_string(section_id);
if (section_id == "")
return "";
let domain = uci_core.get(CONFIG_NAME, section_id, "domain");
if (type(domain) == "array" && length(domain) > 0)
return as_string(domain[0]);
if (type(domain) == "string" && trim(domain) != "") {
let parts = split(trim(domain), /[ \t\r\n]+/);
if (length(parts) > 0)
return parts[0];
}
return "";
}
function get_candidate_strategies(mode) {
mode = as_string(mode || "express");
let base_http = "--filter-tcp=80 --filter-l7=http --payload=http_req ";
let base_tls = "--new --filter-tcp=443 --filter-l7=tls --payload=tls_client_hello ";
let base_quic = "--new --filter-udp=443 --filter-l7=quic --payload=quic_initial ";
let presets = [
{
id: "split2_basic",
name: "Basic Split2",
family: "split2",
opt: base_http + "--lua-desync=split2:pos=method+2 " +
base_tls + "--lua-desync=split2:pos=1,midsld " +
base_quic + "--lua-desync=fake:blob=fake_default_quic:repeats=6"
},
{
id: "disorder2_basic",
name: "Basic Disorder2",
family: "disorder2",
opt: base_http + "--lua-desync=disorder2:pos=method+2 " +
base_tls + "--lua-desync=disorder2:pos=1,midsld " +
base_quic + "--lua-desync=fake:blob=fake_default_quic:repeats=6"
},
{
id: "fake_split2_md5",
name: "Fake + Split2 (MD5Sig)",
family: "fake_split2",
opt: base_http + "--lua-desync=fake:blob=fake_default_http:tcp_md5 --lua-desync=split2:pos=method+2 " +
base_tls + "--lua-desync=fake:blob=fake_default_tls:tcp_md5:tcp_seq=-10000 --lua-desync=split2:pos=1,midsld " +
base_quic + "--lua-desync=fake:blob=fake_default_quic:repeats=6"
},
{
id: "fake_disorder2_md5",
name: "Fake + Disorder2 (MD5Sig)",
family: "fake_disorder2",
opt: base_http + "--lua-desync=fake:blob=fake_default_http:tcp_md5 --lua-desync=disorder2:pos=method+2 " +
base_tls + "--lua-desync=fake:blob=fake_default_tls:tcp_md5:tcp_seq=-10000 --lua-desync=multidisorder:pos=1,midsld " +
base_quic + "--lua-desync=fake:blob=fake_default_quic:repeats=6"
},
{
id: "multisplit_default",
name: "Multisplit (Default Preset)",
family: "multisplit",
opt: base_http + "--lua-desync=fake:blob=fake_default_http:tcp_md5 --lua-desync=multisplit:pos=method+2 " +
base_tls + "--lua-desync=fake:blob=fake_default_tls:tcp_md5:tcp_seq=-10000 --lua-desync=multidisorder:pos=1,midsld " +
base_quic + "--lua-desync=fake:blob=fake_default_quic:repeats=6"
},
{
id: "syndata_split",
name: "Syndata + Split",
family: "syndata",
opt: base_http + "--lua-desync=syndata,split2:pos=method+2 " +
base_tls + "--lua-desync=syndata,split2:pos=1,sni,midsld " +
base_quic + "--lua-desync=fake:blob=fake_default_quic:repeats=6"
},
{
id: "fakedsplit_tls",
name: "Fakedsplit + Multidisorder",
family: "fakedsplit",
opt: base_http + "--lua-desync=fake:blob=fake_default_http:tcp_md5 --lua-desync=split2:pos=method+2 " +
base_tls + "--lua-desync=fakedsplit:blob=fake_default_tls:pos=1,sni,midsld --lua-desync=multidisorder:pos=1,midsld " +
base_quic + "--lua-desync=fake:blob=fake_default_quic:repeats=6"
}
];
if (mode == "deep") {
push(presets, {
id: "fake_split2_ttl",
name: "Fake + Split2 (TTL Auto)",
family: "fake_split2",
opt: base_http + "--lua-desync=fake:blob=fake_default_http:ttl=3 --lua-desync=split2:pos=method+2 " +
base_tls + "--lua-desync=fake:blob=fake_default_tls:ttl=3:tcp_seq=-10000 --lua-desync=split2:pos=1,sni " +
base_quic + "--lua-desync=fake:blob=fake_default_quic:repeats=6"
});
push(presets, {
id: "multisplit_sni_pos",
name: "Multisplit (SNI Position)",
family: "multisplit",
opt: base_http + "--lua-desync=multisplit:pos=method+2 " +
base_tls + "--lua-desync=multisplit:pos=1,sni,midsld " +
base_quic + "--lua-desync=fake:blob=fake_default_quic:repeats=6"
});
}
return presets;
}
function test_domain_probe(domain, opt) {
domain = as_string(domain);
if (domain == "")
return { success: false, rtt_ms: 0, message: "Empty domain" };
let port = 443;
let url = "https://" + domain + "/";
let start_time = now_seconds();
let cmd = sprintf(
"curl -s -o /dev/null -w '%%{http_code}:%%{time_total}' --connect-timeout 2 --max-time 3 -I %s",
shell_quote(url)
);
let output = trim(command_output(cmd));
let elapsed = int((now_seconds() - start_time) * 1000);
if (output != "") {
let parts = split(output, ":");
if (length(parts) >= 2) {
let code = int(parts[0]);
let time_total = (1.0 * parts[1]) * 1000.0;
if (code > 0 && code < 550) {
return {
success: true,
http_code: code,
rtt_ms: int(time_total > 0 ? time_total : elapsed),
message: "HTTP " + code
};
}
}
}
let nc_cmd = sprintf("nc -w 2 -z %s %d", shell_quote(domain), port);
if (command_status(nc_cmd) == 0) {
return {
success: true,
http_code: 200,
rtt_ms: elapsed,
message: "TCP Port 443 Open"
};
}
return {
success: false,
http_code: 0,
rtt_ms: elapsed,
message: "Connection failed or timed out"
};
}
function update_job_progress(job_path, completed, total, failed, current_item) {
if (job_path == "" || fs.stat(job_path) == null)
return;
let data = read_json_file(job_path);
if (type(data) != "object")
return;
data.progress = {
completed: int(completed),
total: int(total),
failed: int(failed),
current_item: as_string(current_item)
};
data.updated_at = now_seconds();
write_json_file(job_path, data);
}
function run_tune(section_id, target_domain, mode, job_path) {
section_id = as_string(section_id);
target_domain = as_string(target_domain);
mode = as_string(mode || "express");
job_path = as_string(job_path);
if (target_domain == "")
target_domain = get_section_domain(section_id);
if (target_domain == "")
target_domain = "googlevideo.com";
let candidates = get_candidate_strategies(mode);
let total = length(candidates);
let results = [];
let best_candidate = null;
let best_rtt = 999999;
let failed_count = 0;
let failed_families = {};
for (let i = 0; i < total; i++) {
let cand = candidates[i];
let family = cand.family;
if (failed_families[family] === true && mode == "express") {
push(results, {
id: cand.id,
name: cand.name,
family: cand.family,
opt: cand.opt,
success: false,
skipped: true,
rtt_ms: 0,
message: "Skipped (Family pruned)"
});
failed_count++;
update_job_progress(job_path, i + 1, total, failed_count, cand.name);
continue;
}
update_job_progress(job_path, i, total, failed_count, cand.name);
let validation = zapret2_validator.validate_strategy("nfqws2", cand.opt, "");
if (!validation.valid) {
push(results, {
id: cand.id,
name: cand.name,
family: cand.family,
opt: cand.opt,
success: false,
rtt_ms: 0,
message: "Validation error: " + as_string(validation.message)
});
failed_count++;
failed_families[family] = true;
continue;
}
let probe = test_domain_probe(target_domain, cand.opt);
if (probe.success) {
push(results, {
id: cand.id,
name: cand.name,
family: cand.family,
opt: cand.opt,
success: true,
http_code: probe.http_code,
rtt_ms: probe.rtt_ms,
message: probe.message
});
if (probe.rtt_ms < best_rtt) {
best_rtt = probe.rtt_ms;
best_candidate = cand;
}
} else {
push(results, {
id: cand.id,
name: cand.name,
family: cand.family,
opt: cand.opt,
success: false,
rtt_ms: probe.rtt_ms,
message: probe.message
});
failed_count++;
failed_families[family] = true;
}
update_job_progress(job_path, i + 1, total, failed_count, cand.name);
}
if (best_candidate == null && length(results) > 0) {
best_candidate = candidates[0];
}
let output = {
success: true,
section_id,
target_domain,
mode,
winning_strategy: best_candidate ? best_candidate.opt : "",
winning_preset_id: best_candidate ? best_candidate.id : "",
winning_preset_name: best_candidate ? best_candidate.name : "",
best_rtt_ms: best_rtt == 999999 ? 0 : best_rtt,
tested_count: total,
results
};
let cache_data = read_json_file(CACHE_FILE) || {};
if (type(cache_data) != "object")
cache_data = {};
cache_data[target_domain] = {
updated_at: now_seconds(),
winning_strategy: output.winning_strategy,
best_rtt_ms: output.best_rtt_ms
};
write_json_file(CACHE_FILE, cache_data);
return output;
}
function main(argv) {
let mode_cmd = argv[0] || "tune";
if (mode_cmd == "tune" || mode_cmd == "run") {
let section_id = argv[1] || "";
let target_domain = argv[2] || "";
let mode = argv[3] || "express";
let job_path = argv[4] || "";
let res = run_tune(section_id, target_domain, mode, job_path);
write_json(res);
return 0;
}
if (mode_cmd == "candidates") {
let mode = argv[1] || "express";
write_json({
success: true,
mode,
candidates: get_candidate_strategies(mode)
});
return 0;
}
write_json({
success: false,
message: "Usage: providers/zapret2/tune.uc <tune|candidates> [section_id] [target_domain] [mode] [job_path]"
});
return 1;
}
if (sourcepath(1) != null && sourcepath(1) != "") {
return {
get_candidate_strategies,
run_tune,
test_domain_probe
};
}
return main(ARGV);
+80
View File
@@ -17,6 +17,7 @@ const SERVICE_ACTION_LOCK_DIR = getenv("TACHYON_UI_SERVICE_ACTION_LOCK_DIR") ||
const LATENCY_ACTION_DIR = getenv("TACHYON_UI_LATENCY_ACTION_DIR") || STATE_DIR + "/latency-actions";
const COMPONENT_ACTION_DIR = getenv("TACHYON_UI_COMPONENT_ACTION_DIR") || getenv("UPDATES_JOB_DIR") || "/var/run/tachyon/component-actions";
const SUBSCRIPTION_ACTION_DIR = getenv("TACHYON_UI_SUBSCRIPTION_ACTION_DIR") || getenv("TACHYON_SUBSCRIPTION_UPDATE_JOB_DIR") || "/var/run/tachyon/subscription-update-jobs";
const ZAPRET2_TUNE_ACTION_DIR = getenv("TACHYON_UI_ZAPRET2_TUNE_ACTION_DIR") || STATE_DIR + "/zapret2-tune-actions";
const SING_BOX_VERSION_CACHE_FILE = getenv("TACHYON_UI_SING_BOX_VERSION_CACHE_FILE") || STATE_DIR + "/sing-box-version";
const SING_BOX_VERSION_CACHE_LOCK_DIR = getenv("TACHYON_UI_SING_BOX_VERSION_CACHE_LOCK_DIR") || SING_BOX_VERSION_CACHE_FILE + ".lock";
const SING_BOX_VARIANT_STATE_FILE = getenv("TACHYON_UI_SING_BOX_VARIANT_STATE_FILE") || "/etc/tachyon/sing-box-variant";
@@ -1476,6 +1477,77 @@ function latency_test_status(job_id_value) {
print(as_string(fs.readfile(path)));
}
function zapret2_tune_worker(path, section_id, target_domain, tune_mode) {
let tune_module = LIB_DIR + "/providers/zapret2/tune.uc";
let cmd = command_from_args([ "ucode", "-L", LIB_DIR, tune_module, "tune", section_id, target_domain, tune_mode, path ]);
let status = command_status(cmd + " >/dev/null 2>&1");
if (status == 0)
write_finished_action_state(path, true, "zapret2 strategy tuning completed", status);
else
write_finished_action_state(path, false, "zapret2 strategy tuning failed", status);
}
function running_zapret2_tune_action_value(section_id, target_domain, tune_mode, started_at) {
return {
success: true,
running: true,
kind: "zapret2_tune",
section: as_string(section_id),
target_domain: as_string(target_domain),
tune_mode: as_string(tune_mode),
message: "zapret2 strategy tuning is running",
pid: null,
started_at: arg_number(started_at),
updated_at: null,
exit_code: null,
progress: {
completed: 0,
total: 7,
failed: 0,
current_item: "Initializing..."
}
};
}
function zapret2_tune_async(section_id, target_domain, tune_mode) {
section_id = as_string(section_id);
target_domain = as_string(target_domain);
tune_mode = as_string(tune_mode || "express");
ensure_dirs();
let id = job_id();
let path = job_state_path_value(ZAPRET2_TUNE_ACTION_DIR, id);
if (path == "" || !write_state_file(path, running_zapret2_tune_action_value(section_id, target_domain, tune_mode, now_seconds()))) {
action_start_response(false, "", "Failed to write zapret2 tune action state");
exit(1);
}
let pid = launch_worker([ "zapret2-tune-worker", path, section_id, target_domain, tune_mode ]);
if (pid == "" || !set_running_job_pid_file(path, pid)) {
if (pid != "")
command_success_from_args([ "kill", pid ]);
action_start_response(false, "", "Failed to write zapret2 tune worker pid");
exit(1);
}
action_start_response(true, id, "zapret2 strategy tuning started");
}
function zapret2_tune_status(job_id_value) {
let path = job_state_path_value(ZAPRET2_TUNE_ACTION_DIR, job_id_value);
if (path == "") {
action_start_response(false, "", "Invalid zapret2 tune job id");
exit(1);
}
if (fs.stat(path) == null) {
action_start_response(false, "", "zapret2 tune job was not found");
exit(1);
}
refresh_pid_job_state(path, "zapret2 tune worker exited unexpectedly");
print(as_string(fs.readfile(path)));
}
function action_dir(kind) {
kind = as_string(kind);
if (kind == "service")
@@ -1486,6 +1558,8 @@ function action_dir(kind) {
return COMPONENT_ACTION_DIR;
if (kind == "subscription")
return SUBSCRIPTION_ACTION_DIR;
if (kind == "zapret2_tune")
return ZAPRET2_TUNE_ACTION_DIR;
return "";
}
@@ -1587,6 +1661,12 @@ else if (mode == "latency-test-async")
latency_test_async(ARGV[1], ARGV[2], ARGV[3], ARGV[4]);
else if (mode == "latency-test-status")
latency_test_status(ARGV[1]);
else if (mode == "zapret2-tune-worker")
zapret2_tune_worker(ARGV[1], ARGV[2], ARGV[3], ARGV[4]);
else if (mode == "zapret2-tune-async")
zapret2_tune_async(ARGV[1], ARGV[2], ARGV[3]);
else if (mode == "zapret2-tune-status")
zapret2_tune_status(ARGV[1]);
else if (mode == "action-ack")
action_ack(ARGV[1], ARGV[2]);
else if (mode == "cleanup-action-dir-fixture")
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
set -eo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
TUNE_MODULE="$ROOT_DIR/tachyon/files/usr/lib/providers/zapret2/tune.uc"
CLI="$ROOT_DIR/tachyon/files/usr/bin/tachyon"
WORK_DIR="$(mktemp -d)"
cleanup() {
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
json_field() {
local json="$1"
local field="$2"
JSON_VALUE="$json" node - "$field" <<'NODE'
const field = process.argv[2];
const value = JSON.parse(process.env.JSON_VALUE);
const actual = value[field];
if (Array.isArray(actual)) {
console.log(actual.join("\n"));
} else {
console.log(String(actual));
}
NODE
}
assert_json_field() {
local json="$1"
local field="$2"
local expected="$3"
local actual
actual="$(json_field "$json" "$field")"
[ "$actual" = "$expected" ] || fail "expected $field=$expected, got $actual"
}
# 1. Test candidates generation
candidates="$(ucode -L "$TACHYON_LIB" "$TUNE_MODULE" candidates express)"
assert_json_field "$candidates" success true
assert_json_field "$candidates" mode express
# 2. Test tuning run on test domain
tune_result="$(ucode -L "$TACHYON_LIB" "$TUNE_MODULE" tune "" "example.com" express)"
assert_json_field "$tune_result" success true
assert_json_field "$tune_result" target_domain "example.com"
# 3. Test CLI zapret2_tune entrypoint
cli_result="$(TACHYON_LIB="$TACHYON_LIB" ucode -L "$TACHYON_LIB" "$CLI" zapret2_tune "" "example.com" express)"
assert_json_field "$cli_result" success true
printf 'zapret2 tuner tests passed successfully\n'