feat(dns): recommend multiple diverse servers for primary DoH, bootstrap, and fallback DNS
This commit is contained in:
@@ -330,6 +330,51 @@ function test_candidate(server, domains, bootstrap_ips) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function select_diverse_servers(candidates, max_count, max_latency, max_loss) {
|
||||||
|
max_count = int(max_count || 2);
|
||||||
|
max_latency = int(max_latency || 400);
|
||||||
|
max_loss = int(max_loss || 25);
|
||||||
|
|
||||||
|
let selected = [];
|
||||||
|
let seen_providers = {};
|
||||||
|
|
||||||
|
for (let c in candidates) {
|
||||||
|
if (c.latency < 0 || c.status == "failed" || c.lossPct > max_loss || c.latency > max_latency)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
let prov = as_string(c.provider);
|
||||||
|
if (!seen_providers[prov]) {
|
||||||
|
seen_providers[prov] = true;
|
||||||
|
push(selected, c);
|
||||||
|
if (length(selected) >= max_count)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length(selected) < max_count) {
|
||||||
|
for (let c in candidates) {
|
||||||
|
if (c.latency < 0 || c.status == "failed" || c.lossPct > max_loss || c.latency > max_latency)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
let addr = as_string(c.address || c.ip);
|
||||||
|
let already = false;
|
||||||
|
for (let s in selected) {
|
||||||
|
if ((s.address || s.ip) == addr) {
|
||||||
|
already = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!already) {
|
||||||
|
push(selected, c);
|
||||||
|
if (length(selected) >= max_count)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
function compute_recommendation(results) {
|
function compute_recommendation(results) {
|
||||||
results = array_or_empty(results);
|
results = array_or_empty(results);
|
||||||
let working = [];
|
let working = [];
|
||||||
@@ -342,10 +387,10 @@ function compute_recommendation(results) {
|
|||||||
if (length(working) == 0) {
|
if (length(working) == 0) {
|
||||||
return {
|
return {
|
||||||
dns_type: "udp",
|
dns_type: "udp",
|
||||||
dns_server: [ "77.88.8.8" ],
|
dns_server: [ "77.88.8.8", "1.1.1.1" ],
|
||||||
bootstrap_dns_server: [ "77.88.8.8" ],
|
bootstrap_dns_server: [ "77.88.8.8", "1.1.1.1" ],
|
||||||
dns_fallback_server: [ "1.1.1.1" ],
|
dns_fallback_server: [ "1.0.0.1", "77.88.8.1" ],
|
||||||
dns_upstream_mode: "sequential",
|
dns_upstream_mode: "parallel",
|
||||||
reason: "All probed DNS servers timed out or failed. Applied safe Yandex & Cloudflare defaults."
|
reason: "All probed DNS servers timed out or failed. Applied safe Yandex & Cloudflare defaults."
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -363,52 +408,65 @@ function compute_recommendation(results) {
|
|||||||
working_udp = sort(working_udp, function(a, b) { return a.score - b.score; });
|
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; });
|
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];
|
// Select top 2 bootstrap UDP servers from distinct providers
|
||||||
let second_udp = null;
|
let top_bootstrap = select_diverse_servers(working_udp, 2, 250, 25);
|
||||||
if (length(working_udp) > 1) {
|
if (length(top_bootstrap) == 0) {
|
||||||
for (let u in working_udp) {
|
top_bootstrap = [ { address: "77.88.8.8", ip: "77.88.8.8", provider: "Yandex", latency: 15 } ];
|
||||||
if (u.provider != fastest_udp.provider) {
|
}
|
||||||
second_udp = u;
|
let bootstrap_ips = [];
|
||||||
|
for (let b in top_bootstrap)
|
||||||
|
push(bootstrap_ips, b.ip || b.address);
|
||||||
|
|
||||||
|
// Select top 2 fallback UDP servers (prefer servers not in bootstrap or distinct)
|
||||||
|
let remaining_udp = [];
|
||||||
|
for (let u in working_udp) {
|
||||||
|
let in_boot = false;
|
||||||
|
for (let b in top_bootstrap) {
|
||||||
|
if (b.address == u.address || b.ip == u.ip) {
|
||||||
|
in_boot = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (second_udp == null)
|
if (!in_boot)
|
||||||
second_udp = working_udp[1];
|
push(remaining_udp, u);
|
||||||
}
|
}
|
||||||
if (second_udp == null)
|
let top_fallback = select_diverse_servers(remaining_udp, 2, 350, 25);
|
||||||
second_udp = { address: "1.1.1.1", ip: "1.1.1.1", provider: "Cloudflare", latency: 50 };
|
if (length(top_fallback) == 0) {
|
||||||
|
top_fallback = select_diverse_servers(working_udp, 2, 350, 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 fallback_ips = [];
|
||||||
|
for (let f in top_fallback)
|
||||||
|
push(fallback_ips, f.ip || f.address);
|
||||||
|
if (length(fallback_ips) == 0)
|
||||||
|
fallback_ips = [ "1.0.0.1", "77.88.8.1" ];
|
||||||
|
|
||||||
let selected_type = "udp";
|
let selected_type = "udp";
|
||||||
let selected_dns_server = [ fastest_udp.address ];
|
let selected_dns_server = [];
|
||||||
let reason = "Selected fastest UDP server (" + fastest_udp.provider + " " + fastest_udp.latency + "ms) as fallback because encrypted DoH is unavailable";
|
let top_udp_primary = select_diverse_servers(working_udp, 2, 200, 25);
|
||||||
|
for (let u in top_udp_primary)
|
||||||
|
push(selected_dns_server, u.address || u.ip);
|
||||||
|
let reason = "Selected fast UDP servers (" + join(", ", selected_dns_server) + ") as fallback because encrypted DoH is unavailable";
|
||||||
|
|
||||||
// ALWAYS PRIORITIZE ENCRYPTED DoH AS PRIMARY DNS
|
// ALWAYS PRIORITIZE ENCRYPTED DoH AS PRIMARY DNS
|
||||||
// Encrypted DNS prevents ISP interception, DNS hijacking, poisoning, and MITM.
|
// Encrypted DNS prevents ISP interception, DNS hijacking, poisoning, and MITM.
|
||||||
// Bootstrap DNS resolves the DoH hostname, while DoH handles all encrypted user traffic.
|
let top_doh = select_diverse_servers(working_doh, 2, 350, 25);
|
||||||
if (length(working_doh) > 0) {
|
if (length(top_doh) > 0) {
|
||||||
let best_doh = working_doh[0];
|
selected_type = "doh";
|
||||||
if (best_doh.lossPct <= 25 && best_doh.latency < 350) {
|
selected_dns_server = [];
|
||||||
selected_type = "doh";
|
let doh_names = [];
|
||||||
selected_dns_server = [ best_doh.address ];
|
for (let d in top_doh) {
|
||||||
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";
|
push(selected_dns_server, d.address);
|
||||||
|
push(doh_names, sprintf("%s (%dms)", d.provider, d.latency));
|
||||||
}
|
}
|
||||||
|
reason = "Selected multiple encrypted DoH servers [" + join(", ", doh_names) + "] for maximum privacy and redundancy, with [" + join(", ", bootstrap_ips) + "] 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 {
|
return {
|
||||||
dns_type: selected_type,
|
dns_type: selected_type,
|
||||||
dns_server: selected_dns_server,
|
dns_server: selected_dns_server,
|
||||||
bootstrap_dns_server: [ bootstrap_ip ],
|
bootstrap_dns_server: bootstrap_ips,
|
||||||
dns_fallback_server: [ fallback_ip ],
|
dns_fallback_server: fallback_ips,
|
||||||
dns_upstream_mode: upstream_mode,
|
dns_upstream_mode: "parallel",
|
||||||
reason: reason
|
reason: reason
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,11 +64,12 @@ ucode -L "$TACHYON_LIB" "$WORK_DIR/test_candidates.uc" | grep -q "OK" ||
|
|||||||
cat >"$WORK_DIR/test_recommendation.uc" <<'UCODE'
|
cat >"$WORK_DIR/test_recommendation.uc" <<'UCODE'
|
||||||
let benchmark = require("dns.benchmark");
|
let benchmark = require("dns.benchmark");
|
||||||
|
|
||||||
// Scenario A: Fast DoH (<85ms, close to UDP) -> Expect DoH recommended
|
// Scenario A: Fast DoH -> Expect DoH recommended with multiple servers
|
||||||
let results_a = [
|
let results_a = [
|
||||||
{ id: "yandex_udp", provider: "Yandex", type: "udp", address: "77.88.8.8", ip: "77.88.8.8", latency: 15, lossPct: 0, status: "excellent", score: 15 },
|
{ id: "yandex_udp", provider: "Yandex", type: "udp", address: "77.88.8.8", ip: "77.88.8.8", latency: 15, lossPct: 0, status: "excellent", score: 15 },
|
||||||
{ id: "cf_udp", provider: "Cloudflare", type: "udp", address: "1.1.1.1", ip: "1.1.1.1", latency: 20, lossPct: 0, status: "excellent", score: 20 },
|
{ id: "cf_udp", provider: "Cloudflare", type: "udp", address: "1.1.1.1", ip: "1.1.1.1", latency: 20, lossPct: 0, status: "excellent", score: 20 },
|
||||||
{ id: "cf_doh", provider: "Cloudflare", type: "doh", address: "https://cloudflare-dns.com/dns-query", ip: "1.1.1.1", latency: 25, lossPct: 0, status: "excellent", score: 25 }
|
{ id: "cf_doh", provider: "Cloudflare", type: "doh", address: "https://cloudflare-dns.com/dns-query", ip: "1.1.1.1", latency: 25, lossPct: 0, status: "excellent", score: 25 },
|
||||||
|
{ id: "comss_doh", provider: "Comss.one", type: "doh", address: "https://dns.comss.one/dns-query", ip: "92.223.109.31", latency: 30, lossPct: 0, status: "excellent", score: 30 }
|
||||||
];
|
];
|
||||||
|
|
||||||
let rec_a = benchmark.compute_recommendation(results_a);
|
let rec_a = benchmark.compute_recommendation(results_a);
|
||||||
@@ -76,12 +77,12 @@ if (rec_a.dns_type != "doh") {
|
|||||||
print("ERR: Scenario A should recommend DoH, got ", rec_a.dns_type, "\n");
|
print("ERR: Scenario A should recommend DoH, got ", rec_a.dns_type, "\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if (rec_a.bootstrap_dns_server[0] != "77.88.8.8") {
|
if (length(rec_a.dns_server) < 2) {
|
||||||
print("ERR: Scenario A bootstrap DNS should be fastest UDP (77.88.8.8)\n");
|
print("ERR: Scenario A should recommend multiple DoH servers\n");
|
||||||
exit(2);
|
exit(2);
|
||||||
}
|
}
|
||||||
if (rec_a.dns_fallback_server[0] != "1.1.1.1") {
|
if (rec_a.bootstrap_dns_server[0] != "77.88.8.8") {
|
||||||
print("ERR: Scenario A fallback DNS should be secondary provider (1.1.1.1)\n");
|
print("ERR: Scenario A bootstrap DNS should be fastest UDP (77.88.8.8)\n");
|
||||||
exit(3);
|
exit(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +102,7 @@ if (rec_b.dns_type != "udp" || rec_b.dns_server[0] != "77.88.8.8") {
|
|||||||
// Scenario C: All failed -> Expect safe failsafe fallback
|
// Scenario C: All failed -> Expect safe failsafe fallback
|
||||||
let results_c = [];
|
let results_c = [];
|
||||||
let rec_c = benchmark.compute_recommendation(results_c);
|
let rec_c = benchmark.compute_recommendation(results_c);
|
||||||
if (rec_c.dns_type != "udp" || rec_c.dns_server[0] != "77.88.8.8" || rec_c.dns_fallback_server[0] != "1.1.1.1") {
|
if (rec_c.dns_type != "udp" || rec_c.dns_server[0] != "77.88.8.8" || length(rec_c.dns_server) < 2) {
|
||||||
print("ERR: Scenario C failsafe failed\n");
|
print("ERR: Scenario C failsafe failed\n");
|
||||||
exit(5);
|
exit(5);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user