fix(dns): two-phase cascade benchmark with verified bootstrap DNS resolution

This commit is contained in:
Dushnilin
2026-09-02 16:25:58 +03:00
parent d72e381018
commit ad960a173a
+118 -17
View File
@@ -121,6 +121,44 @@ function domain_to_doh_b64(domain) {
return res;
}
// Resolves a hostname via a specific bootstrap UDP server.
function resolve_host_via_bootstrap(host, bootstrap_ip) {
host = as_string(host);
bootstrap_ip = as_string(bootstrap_ip);
if (match(host, /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/))
return host;
if (has_tool("dig")) {
let output = command_output_from_args([
"dig", "@" + bootstrap_ip, host, "A", "+short", "+time=2", "+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]+$/))
return line;
}
}
}
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]+\.[0-9]+\.[0-9]+\.[0-9]+)/);
if (m && m[1] != null && m[1] != bootstrap_ip)
return m[1];
}
let m2 = match(output, /Name:[ \t]+[^\n]+\nAddress:[ \t]+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)/);
if (m2 && m2[1] != null)
return m2[1];
}
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) {
@@ -155,14 +193,28 @@ function probe_udp(server_ip, domain) {
return -1;
}
// Probes a DoH DNS server using `curl` with standard RFC 8484 DNS wireformat and direct IP resolving.
// Probes a DoH DNS server using `curl` with standard RFC 8484 DNS wireformat and bootstrap IP resolving.
// Returns latency in milliseconds, or -1 on failure.
function probe_doh(server, domain) {
function probe_doh(server, domain, bootstrap_ips) {
let doh_url = as_string(type(server) == "object" ? server.address : server);
domain = as_string(domain);
let ip = as_string(type(server) == "object" ? (server.ip || "") : "");
let fallback_ip = as_string(type(server) == "object" ? (server.ip || "") : "");
let host = url_host(doh_url);
// Resolve DoH host using verified bootstrap DNS servers (cascade)
let resolved_ip = null;
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);
if (resolved_ip != null)
break;
}
}
if (resolved_ip == null)
resolved_ip = fallback_ip;
let b64 = domain_to_doh_b64(domain);
let query_url = doh_url + (index(doh_url, "?") >= 0 ? "&" : "?") + "dns=" + b64;
@@ -171,10 +223,9 @@ function probe_doh(server, domain) {
"-H", "accept: application/dns-message, application/dns-json"
];
// Directly resolve host to the provider IP to test DoH over direct UDP bootstrap without local DNS dependency
if (host != "" && ip != "") {
push(args, "--resolve", host + ":443:" + ip);
push(args, "--resolve", host + ":80:" + ip);
if (host != "" && resolved_ip != "") {
push(args, "--resolve", host + ":443:" + resolved_ip);
push(args, "--resolve", host + ":80:" + resolved_ip);
}
push(args, query_url);
@@ -196,20 +247,20 @@ function probe_doh(server, domain) {
return -1;
}
function probe_server(server, domain) {
function probe_server(server, domain, bootstrap_ips) {
if (server.type == "doh")
return probe_doh(server, domain);
return probe_doh(server, domain, bootstrap_ips);
return probe_udp(server.ip || server.address, domain);
}
function test_candidate(server, domains) {
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);
for (let domain in domains) {
let ms = probe_server(server, domain);
let ms = probe_server(server, domain, bootstrap_ips);
if (ms >= 0) {
total_ms += ms;
success_count++;
@@ -336,19 +387,69 @@ function compute_recommendation(results) {
}
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 = [];
let total = length(CANDIDATE_SERVERS);
for (let i = 0; i < total; i++) {
let server = CANDIDATE_SERVERS[i];
// --- 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(i, total, server.provider + " (" + server.type + ")", null);
on_progress(current_index, total, server.provider + " (UDP)", null);
let res = test_candidate(server, TEST_DOMAINS);
let res = test_candidate(server, TEST_DOMAINS, []);
push(udp_results, res);
push(results, res);
current_index++;
if (on_progress)
on_progress(i + 1, total, server.provider + " (" + server.type + ")", res);
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)