Files
NeoTachyon/tachyon/files/usr/lib/dns/benchmark.uc
T

749 lines
26 KiB
Ucode

#!/usr/bin/env ucode
let fs = require("fs");
let common = require("core.common");
let uci_core = require("core.uci");
let as_string = common.as_string;
let read_json_file = common.read_json_file;
let write_json_file = common.write_json_file;
let command_output_from_args = common.command_output_from_args;
let command_status_from_args = common.command_status_from_args;
let command_success_from_args = common.command_success_from_args;
let command_from_args = common.command_from_args;
let shell_quote = common.shell_quote;
let object_or_empty = common.object_or_empty;
let array_or_empty = common.array_or_empty;
let option = common.option;
let list_option = common.list_option;
let normalize_status = common.normalize_status;
const CONFIG_NAME = getenv("TACHYON_CONFIG_NAME") || "tachyon";
const LIB_DIR = getenv("TACHYON_LIB") || "/usr/lib/tachyon";
const STATE_DIR = getenv("TACHYON_UI_STATE_DIR") || "/var/run/tachyon";
const BENCHMARK_STATE_FILE = STATE_DIR + "/dns-benchmark-state.json";
const BENCHMARK_PID_FILE = STATE_DIR + "/dns-benchmark.pid";
const SERVICE_INIT = getenv("TACHYON_SERVICE_INIT") || "/etc/init.d/tachyon";
const CANDIDATE_SERVERS = [
// --- Yandex DNS ---
{ id: "yandex_udp", provider: "Yandex", type: "udp", address: "77.88.8.8", ip: "77.88.8.8", tag: "Primary" },
{ id: "yandex_udp2", provider: "Yandex", type: "udp", address: "77.88.8.1", ip: "77.88.8.1", tag: "Secondary" },
// --- Cloudflare DNS ---
{ id: "cloudflare_udp", provider: "Cloudflare", type: "udp", address: "1.1.1.1", ip: "1.1.1.1", tag: "Primary" },
{ id: "cloudflare_udp2", provider: "Cloudflare", type: "udp", address: "1.0.0.1", ip: "1.0.0.1", tag: "Secondary" },
{ id: "cloudflare_doh", provider: "Cloudflare", type: "doh", address: "https://cloudflare-dns.com/dns-query", ip: "1.1.1.1", tag: "DoH Encrypted" },
// --- Google Public DNS ---
{ id: "google_udp", provider: "Google", type: "udp", address: "8.8.8.8", ip: "8.8.8.8", tag: "Primary" },
{ id: "google_udp2", provider: "Google", type: "udp", address: "8.8.4.4", ip: "8.8.4.4", tag: "Secondary" },
{ id: "google_doh", provider: "Google", type: "doh", address: "https://dns.google/dns-query", ip: "8.8.8.8", tag: "DoH Encrypted" },
// --- AdGuard DNS ---
{ id: "adguard_udp", provider: "AdGuard", type: "udp", address: "94.140.14.14", ip: "94.140.14.14", tag: "Default" },
{ id: "adguard_doh", provider: "AdGuard", type: "doh", address: "https://dns.adguard-dns.com/dns-query", ip: "94.140.14.14", tag: "DoH AdBlock" },
// --- Comss.one DNS ---
{ id: "comss_udp", provider: "Comss.one", type: "udp", address: "92.223.109.31", ip: "92.223.109.31", tag: "Anti-Censorship" },
{ id: "comss_doh", provider: "Comss.one", type: "doh", address: "https://dns.comss.one/dns-query", ip: "92.223.109.31", tag: "DoH Anti-Censorship" },
// --- Control D ---
{ id: "controld_udp", provider: "Control D", type: "udp", address: "76.76.2.0", ip: "76.76.2.0", tag: "Unfiltered" },
{ id: "controld_doh", provider: "Control D", type: "doh", address: "https://freedns.controld.com/p0", ip: "76.76.2.0", tag: "DoH Encrypted" },
// --- Quad9 ---
{ id: "quad9_udp", provider: "Quad9", type: "udp", address: "9.9.9.9", ip: "9.9.9.9", tag: "Primary" },
{ id: "quad9_doh", provider: "Quad9", type: "doh", address: "https://dns.quad9.net/dns-query", ip: "9.9.9.9", tag: "DoH Secure" },
// --- Mullvad DNS ---
{ id: "mullvad_udp", provider: "Mullvad", type: "udp", address: "194.242.2.2", ip: "194.242.2.2", tag: "Privacy" },
{ id: "mullvad_doh", provider: "Mullvad", type: "doh", address: "https://dns.mullvad.net/dns-query", ip: "194.242.2.2", tag: "DoH Privacy" },
// --- OpenDNS ---
{ id: "opendns_udp", provider: "OpenDNS", type: "udp", address: "208.67.222.222", ip: "208.67.222.222", tag: "Standard" },
{ id: "opendns_doh", provider: "OpenDNS", type: "doh", address: "https://doh.opendns.com/dns-query", ip: "208.67.222.222", tag: "DoH Encrypted" }
];
const TEST_DOMAINS = [
"google.com",
"yandex.ru"
];
function now_seconds() {
let stamp = clock();
return stamp ? stamp[0] : 0;
}
function has_tool(name) {
let out = command_output_from_args([ "which", as_string(name) ]);
return length(out) > 0;
}
function url_host(url) {
let m = match(as_string(url), /^https?:\/\/([^\/:]+)/);
return m ? m[1] : "";
}
function domain_to_doh_b64(domain) {
domain = trim(as_string(domain));
if (domain == "google.com") return "AAABAAABAAAAAAAABmdvb2dsZQNjb20AAAEAAQ";
if (domain == "cloudflare.com") return "AAABAAABAAAAAAAACmNsb3VkZmxhcmUDY29tAAABAAE";
if (domain == "wikipedia.org") return "AAABAAABAAAAAAAACXdpa2lwZWRpYQNvcmcAAAEAAQ";
if (domain == "yandex.ru") return "AAABAAABAAAAAAAABnlhbmRleAJydQAAAQAB";
if (domain == "example.com") return "AAABAAABAAAAAAAAB2V4YW1wbGUDY29tAAABAAE";
let labels = split(replace(domain, /^\.+|\.+$/, ""), ".");
let bytes = [ 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0 ];
for (let label in labels) {
if (label == "") continue;
push(bytes, length(label));
for (let i = 0; i < length(label); i++)
push(bytes, ord(label, i));
}
push(bytes, 0, 0, 1, 0, 1);
let b64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
let res = "";
let i = 0;
while (i < length(bytes)) {
let b0 = bytes[i];
let b1 = i + 1 < length(bytes) ? bytes[i + 1] : 0;
let b2 = i + 2 < length(bytes) ? bytes[i + 2] : 0;
let c0 = (b0 >> 2) & 0x3f;
let c1 = (((b0 & 0x03) << 4) | ((b1 >> 4) & 0x0f)) & 0x3f;
let c2 = (((b1 & 0x0f) << 2) | ((b2 >> 6) & 0x03)) & 0x3f;
let c3 = b2 & 0x3f;
res += substr(b64chars, c0, 1);
res += substr(b64chars, c1, 1);
if (i + 1 < length(bytes))
res += substr(b64chars, c2, 1);
if (i + 2 < length(bytes))
res += substr(b64chars, c3, 1);
i += 3;
}
return res;
}
let resolved_hosts_cache = {};
// Resolves a hostname via a specific bootstrap UDP server with cache and fast fallback.
function resolve_host_via_bootstrap(host, bootstrap_ip, fallback_ip) {
host = as_string(host);
bootstrap_ip = as_string(bootstrap_ip);
fallback_ip = as_string(fallback_ip);
if (match(host, /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/))
return host;
let cache_key = host + "@" + bootstrap_ip;
if (resolved_hosts_cache[cache_key])
return resolved_hosts_cache[cache_key];
if (has_tool("dig")) {
let output = command_output_from_args([
"dig", "@" + bootstrap_ip, host, "A", "+short", "+time=1", "+tries=1"
]);
if (length(output) > 0) {
let lines = split(output, "\n");
for (let line in lines) {
line = trim(line);
if (match(line, /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/)) {
resolved_hosts_cache[cache_key] = line;
return line;
}
}
}
}
if (has_tool("nslookup")) {
let output = command_output_from_args([ "nslookup", host, bootstrap_ip ]);
if (length(output) > 0) {
let lines = split(output, "\n");
for (let line in lines) {
let m = match(line, /Address(?:es)?(?:[ \t]+[0-9]+)?:[ \t]+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/);
if (m && m[1] != null && m[1] != bootstrap_ip) {
resolved_hosts_cache[cache_key] = m[1];
return m[1];
}
}
let m2 = match(output, /Name:[ \t]+[^\n]+\nAddress(?:[ \t]+[0-9]+)?:[ \t]+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/);
if (m2 && m2[1] != null && m2[1] != bootstrap_ip) {
resolved_hosts_cache[cache_key] = m2[1];
return m2[1];
}
}
}
if (fallback_ip != "") {
resolved_hosts_cache[cache_key] = fallback_ip;
return fallback_ip;
}
return null;
}
// Probes a UDP DNS server with `dig` (primary) or `nslookup` (fallback).
// Returns latency in milliseconds, or -1 on failure.
function probe_udp(server_ip, domain) {
server_ip = as_string(server_ip);
domain = as_string(domain);
if (has_tool("dig")) {
let output = command_output_from_args([
"dig", "@" + server_ip, domain, "A", "+time=1", "+tries=1", "+stats"
]);
if (length(output) > 0) {
if (index(output, "status: NOERROR") >= 0 || index(output, "status: NXDOMAIN") >= 0) {
let m = match(output, /;; Query time:[ \t]+([0-9]+)[ \t]+msec/);
if (m && m[1] != null) {
let ms = int(m[1]);
return ms >= 0 ? ms : 0;
}
}
}
}
// Fallback using nslookup & timestamp
let t0 = clock();
let status = command_status_from_args([ "nslookup", domain, server_ip ]);
let t1 = clock();
if (status == 0 && t0 && t1) {
let elapsed_ms = (t1[0] - t0[0]) * 1000 + int((t1[1] - t0[1]) / 1000000);
return elapsed_ms >= 0 ? elapsed_ms : 0;
}
return -1;
}
// Probes a DoH DNS server using `curl` with standard RFC 8484 DNS wireformat.
// Returns latency in milliseconds, or -1 on failure.
function probe_doh(server, domain, resolved_ip) {
let doh_url = as_string(type(server) == "object" ? server.address : server);
domain = as_string(domain);
let host = url_host(doh_url);
resolved_ip = as_string(resolved_ip);
let b64 = domain_to_doh_b64(domain);
let query_url = doh_url + (index(doh_url, "?") >= 0 ? "&" : "?") + "dns=" + b64;
let args = [
"curl", "-s", "-k", "-m", "2", "--connect-timeout", "2", "-w", "\n%{http_code} %{time_total}",
"-H", "accept: application/dns-message, application/dns-json"
];
if (host != "" && resolved_ip != "") {
push(args, "--resolve", host + ":443:" + resolved_ip);
push(args, "--resolve", host + ":80:" + resolved_ip);
}
push(args, query_url);
let output = command_output_from_args(args);
if (length(output) > 0) {
let lines = split(output, "\n");
let last_line = length(lines) > 0 ? lines[length(lines) - 1] : "";
if (last_line == "" && length(lines) > 1)
last_line = lines[length(lines) - 2];
let m = match(last_line, /^([0-9]{3})[ \t]+([0-9.]+)/);
if (m && m[1] == "200" && m[2] != null) {
let total_sec = double(m[2]);
let ms = int(total_sec * 1000);
return ms >= 0 ? ms : 0;
}
}
return -1;
}
function test_candidate(server, domains, bootstrap_ips) {
domains = array_or_empty(domains);
let total_ms = 0;
let success_count = 0;
let total_rounds = length(domains);
let resolved_ip = "";
if (server.type == "doh") {
let host = url_host(server.address);
bootstrap_ips = array_or_empty(bootstrap_ips);
for (let b_ip in bootstrap_ips) {
b_ip = as_string(b_ip);
if (b_ip != "") {
resolved_ip = resolve_host_via_bootstrap(host, b_ip, server.ip || "");
if (resolved_ip != null && resolved_ip != "")
break;
}
}
if (resolved_ip == "" || resolved_ip == null)
resolved_ip = as_string(server.ip || "");
}
for (let domain in domains) {
let ms = -1;
if (server.type == "doh")
ms = probe_doh(server, domain, resolved_ip);
else
ms = probe_udp(server.ip || server.address, domain);
if (ms >= 0) {
total_ms += ms;
success_count++;
}
}
let result = {
id: server.id,
provider: server.provider,
type: server.type,
address: server.address,
ip: server.ip,
tag: server.tag,
latency: -1,
lossPct: 100,
status: "failed",
score: 99999
};
if (success_count > 0) {
let avg_latency = int(total_ms / success_count);
let loss_pct = int(((total_rounds - success_count) * 100) / total_rounds);
let score = avg_latency + (loss_pct * 5);
result.latency = avg_latency;
result.lossPct = loss_pct;
result.score = score;
if (avg_latency < 35 && loss_pct == 0)
result.status = "excellent";
else if (avg_latency < 75 && loss_pct <= 25)
result.status = "good";
else if (avg_latency < 150 && loss_pct <= 50)
result.status = "fair";
else if (loss_pct < 100)
result.status = "slow";
else
result.status = "failed";
}
return result;
}
function compute_recommendation(results) {
results = array_or_empty(results);
let working = [];
for (let r in results) {
if (r.latency >= 0 && r.status != "failed")
push(working, r);
}
// Default failsafe recommendation
if (length(working) == 0) {
return {
dns_type: "udp",
dns_server: [ "77.88.8.8" ],
bootstrap_dns_server: [ "77.88.8.8" ],
dns_fallback_server: [ "1.1.1.1" ],
dns_upstream_mode: "sequential",
reason: "All probed DNS servers timed out or failed. Applied safe Yandex & Cloudflare defaults."
};
}
let working_udp = [];
let working_doh = [];
for (let r in working) {
if (r.type == "udp")
push(working_udp, r);
else if (r.type == "doh")
push(working_doh, r);
}
working_udp = sort(working_udp, function(a, b) { return a.score - b.score; });
working_doh = sort(working_doh, function(a, b) { return a.score - b.score; });
let fastest_udp = length(working_udp) > 0 ? working_udp[0] : working[0];
let second_udp = null;
if (length(working_udp) > 1) {
for (let u in working_udp) {
if (u.provider != fastest_udp.provider) {
second_udp = u;
break;
}
}
if (second_udp == null)
second_udp = working_udp[1];
}
if (second_udp == null)
second_udp = { address: "1.1.1.1", ip: "1.1.1.1", provider: "Cloudflare", latency: 50 };
let bootstrap_ip = fastest_udp.ip || fastest_udp.address || "77.88.8.8";
let fallback_ip = second_udp.ip || second_udp.address || "1.1.1.1";
let selected_type = "udp";
let selected_dns_server = [ fastest_udp.address ];
let reason = "Selected fastest UDP server (" + fastest_udp.provider + " " + fastest_udp.latency + "ms) as fallback because encrypted DoH is unavailable";
// ALWAYS PRIORITIZE ENCRYPTED DoH AS PRIMARY DNS
// Encrypted DNS prevents ISP interception, DNS hijacking, poisoning, and MITM.
// Bootstrap DNS resolves the DoH hostname, while DoH handles all encrypted user traffic.
if (length(working_doh) > 0) {
let best_doh = working_doh[0];
if (best_doh.lossPct <= 25 && best_doh.latency < 350) {
selected_type = "doh";
selected_dns_server = [ best_doh.address ];
reason = "Selected encrypted DoH (" + best_doh.provider + " " + best_doh.latency + "ms) for maximum privacy and anti-censorship, with " + fastest_udp.provider + " (" + bootstrap_ip + ") bootstrap DNS";
}
}
let upstream_mode = "sequential";
if (length(working_doh) > 1 && working_doh[0].latency < 70 && working_doh[1].latency < 90)
upstream_mode = "parallel";
else if (fastest_udp.latency < 40 && second_udp.latency < 50)
upstream_mode = "parallel";
return {
dns_type: selected_type,
dns_server: selected_dns_server,
bootstrap_dns_server: [ bootstrap_ip ],
dns_fallback_server: [ fallback_ip ],
dns_upstream_mode: upstream_mode,
reason: reason
};
}
function run_benchmark(on_progress) {
let udp_candidates = [];
let doh_candidates = [];
for (let s in CANDIDATE_SERVERS) {
if (s.type == "udp")
push(udp_candidates, s);
else if (s.type == "doh")
push(doh_candidates, s);
}
let total = length(udp_candidates) + length(doh_candidates);
let current_index = 0;
let results = [];
// --- Phase 1: Benchmark UDP Bootstrap Candidates ---
let udp_results = [];
for (let i = 0; i < length(udp_candidates); i++) {
let server = udp_candidates[i];
if (on_progress)
on_progress(current_index, total, server.provider + " (UDP)", null);
let res = test_candidate(server, TEST_DOMAINS, []);
push(udp_results, res);
push(results, res);
current_index++;
if (on_progress)
on_progress(current_index, total, server.provider + " (UDP)", res);
}
// Find best and secondary working UDP bootstrap servers for Phase 2
let working_udp = [];
for (let r in udp_results) {
if (r.latency >= 0 && r.status != "failed")
push(working_udp, r);
}
working_udp = sort(working_udp, function(a, b) { return a.score - b.score; });
let primary_bootstrap_ip = "77.88.8.8";
let secondary_bootstrap_ip = "1.1.1.1";
if (length(working_udp) > 0)
primary_bootstrap_ip = working_udp[0].ip || working_udp[0].address;
if (length(working_udp) > 1) {
for (let u in working_udp) {
if (u.provider != working_udp[0].provider) {
secondary_bootstrap_ip = u.ip || u.address;
break;
}
}
}
let bootstrap_ips = [ primary_bootstrap_ip, secondary_bootstrap_ip ];
// --- Phase 2: Benchmark Encrypted DoH Servers via Verified Bootstrap DNS ---
for (let i = 0; i < length(doh_candidates); i++) {
let server = doh_candidates[i];
if (on_progress)
on_progress(current_index, total, server.provider + " (DoH)", null);
let res = test_candidate(server, TEST_DOMAINS, bootstrap_ips);
push(results, res);
current_index++;
if (on_progress)
on_progress(current_index, total, server.provider + " (DoH)", res);
}
// Sort by score ascending (lowest score is best)
results = sort(results, function(a, b) {
return a.score - b.score;
});
let recommendation = compute_recommendation(results);
return {
results: results,
recommendation: recommendation
};
}
function apply_recommendation(rec) {
if (!rec || type(rec) != "object")
return false;
let cursor = uci_core.cursor();
if (!cursor)
return false;
if (rec.dns_type)
cursor.set(CONFIG_NAME, "settings", "dns_type", as_string(rec.dns_type));
if (rec.dns_server)
cursor.set(CONFIG_NAME, "settings", "dns_server", rec.dns_server);
if (rec.bootstrap_dns_server)
cursor.set(CONFIG_NAME, "settings", "bootstrap_dns_server", rec.bootstrap_dns_server);
if (rec.dns_fallback_server)
cursor.set(CONFIG_NAME, "settings", "dns_fallback_server", rec.dns_fallback_server);
if (rec.dns_upstream_mode)
cursor.set(CONFIG_NAME, "settings", "dns_upstream_mode", as_string(rec.dns_upstream_mode));
cursor.commit(CONFIG_NAME);
// Restart tachyon service to apply immediately
command_success_from_args([ SERVICE_INIT, "restart" ]);
return true;
}
// --- CLI Output Helpers ---
function print_cli_table(benchmark_data) {
let results = benchmark_data.results;
let rec = benchmark_data.recommendation;
print("\n========================================================================================\n");
print(" TACHYON DNS BENCHMARK RESULTS \n");
print("========================================================================================\n");
printf("%-14s | %-6s | %-42s | %-8s | %-6s | %-10s\n", "Provider", "Type", "Address", "Latency", "Loss", "Rating");
print("----------------------------------------------------------------------------------------\n");
for (let r in results) {
let lat_str = r.latency >= 0 ? sprintf("%d ms", r.latency) : "TIMEOUT";
let loss_str = sprintf("%d%%", r.lossPct);
let rating = r.status == "excellent" ? "EXCELLENT" :
r.status == "good" ? "GOOD" :
r.status == "fair" ? "FAIR" :
r.status == "slow" ? "SLOW" : "FAILED";
printf("%-14s | %-6s | %-42s | %-8s | %-6s | %-10s\n",
substr(r.provider, 0, 14),
r.type,
substr(r.address, 0, 42),
lat_str,
loss_str,
rating
);
}
print("========================================================================================\n\n");
print("⚡ RECOMMENDED TACHYON DNS CONFIGURATION:\n");
print(" • DNS Type: ", rec.dns_type, "\n");
print(" • Primary DNS: ", join(", ", rec.dns_server), "\n");
print(" • Bootstrap DNS: ", join(", ", rec.bootstrap_dns_server), "\n");
print(" • Fallback DNS: ", join(", ", rec.dns_fallback_server), "\n");
print(" • Upstream Mode: ", rec.dns_upstream_mode, "\n");
print(" • Analysis: ", rec.reason, "\n\n");
}
// --- Background / UI Async Engine ---
function write_state(state) {
command_success_from_args([ "mkdir", "-p", STATE_DIR ]);
return write_json_file(BENCHMARK_STATE_FILE, state);
}
function read_state() {
let data = read_json_file(BENCHMARK_STATE_FILE);
return type(data) == "object" ? data : null;
}
function start_async() {
let existing = read_state();
if (existing && existing.running === true) {
let age = now_seconds() - (existing.started_at || 0);
if (age < 90) {
print(sprintf("%J\n", { success: true, message: "DNS benchmark is already running", state: existing }));
return 0;
}
}
let initial_state = {
running: true,
progress: 0,
current_server: "Starting benchmark...",
results: [],
recommendation: null,
error: null,
started_at: now_seconds(),
finished_at: null
};
write_state(initial_state);
// Fork async background worker
let worker_cmd = sprintf("TACHYON_LIB=%s ucode -L %s %s/dns/benchmark.uc worker >/dev/null 2>&1 & echo $!",
shell_quote(LIB_DIR), shell_quote(LIB_DIR), LIB_DIR);
let pipe = fs.popen(worker_cmd, "r");
if (pipe) {
let pid = trim(as_string(pipe.read("all")));
pipe.close();
if (pid != "")
fs.writefile(BENCHMARK_PID_FILE, pid);
}
print(sprintf("%J\n", { success: true, message: "DNS benchmark started", running: true }));
return 0;
}
function run_worker() {
let state = {
running: true,
progress: 0,
current_server: "Initializing...",
results: [],
recommendation: null,
error: null,
started_at: now_seconds(),
finished_at: null
};
write_state(state);
let benchmark_data = run_benchmark(function(completed, total, current_name, latest_result) {
state.progress = int((completed * 100) / total);
state.current_server = current_name;
if (latest_result)
push(state.results, latest_result);
write_state(state);
});
state.running = false;
state.progress = 100;
state.current_server = "Completed";
state.results = benchmark_data.results;
state.recommendation = benchmark_data.recommendation;
state.finished_at = now_seconds();
write_state(state);
try { fs.unlink(BENCHMARK_PID_FILE); } catch(e) {}
return 0;
}
function stop_benchmark() {
let pid_str = fs.readfile(BENCHMARK_PID_FILE);
if (pid_str) {
let pid = int(trim(as_string(pid_str)));
if (pid > 0)
command_status_from_args([ "kill", "-9", as_string(pid) ]);
try { fs.unlink(BENCHMARK_PID_FILE); } catch(e) {}
}
let state = read_state() || {};
state.running = false;
state.error = "Benchmark stopped by user";
state.finished_at = now_seconds();
write_state(state);
print(sprintf("%J\n", { success: true, message: "Benchmark stopped" }));
return 0;
}
function get_status() {
let state = read_state();
if (!state) {
state = {
running: false,
progress: 0,
current_server: "",
results: [],
recommendation: null,
error: null,
started_at: 0,
finished_at: null
};
}
print(sprintf("%J\n", state));
return 0;
}
function apply_from_state() {
let state = read_state();
if (!state || !state.recommendation) {
print(sprintf("%J\n", { success: false, error: "No benchmark recommendation available to apply" }));
return 1;
}
let ok = apply_recommendation(state.recommendation);
if (ok)
print(sprintf("%J\n", { success: true, message: "Recommended DNS configuration applied and service restarted", recommendation: state.recommendation }));
else
print(sprintf("%J\n", { success: false, error: "Failed to apply DNS configuration to UCI" }));
return ok ? 0 : 1;
}
// --- CLI Dispatcher ---
let mode = ARGV[0] || "";
if (mode == "benchmark") {
let json_mode = (ARGV[1] == "--json");
let data = run_benchmark(null);
if (json_mode)
print(sprintf("%J\n", data));
else
print_cli_table(data);
exit(0);
}
else if (mode == "autotune") {
let apply_flag = (ARGV[1] == "--apply" || ARGV[1] == "-a");
let data = run_benchmark(null);
print_cli_table(data);
if (apply_flag) {
print("Applying recommended DNS configuration...\n");
if (apply_recommendation(data.recommendation))
print("✓ Settings saved to /etc/config/tachyon and service restarted successfully.\n");
else
print("✗ Failed to apply settings.\n");
} else {
print("Tip: Run `tachyon dns_autotune --apply` to automatically save and activate these settings.\n");
}
exit(0);
}
else if (mode == "benchmark_async" || mode == "benchmark-async") {
exit(start_async());
}
else if (mode == "worker") {
exit(run_worker());
}
else if (mode == "benchmark_status" || mode == "benchmark-status") {
exit(get_status());
}
else if (mode == "benchmark_stop" || mode == "benchmark-stop") {
exit(stop_benchmark());
}
else if (mode == "benchmark_apply" || mode == "benchmark-apply") {
exit(apply_from_state());
}
return {
CANDIDATE_SERVERS,
TEST_DOMAINS,
probe_udp,
probe_doh,
test_candidate,
compute_recommendation,
run_benchmark,
apply_recommendation
};