feat: DoQ в UI + динамическая фильтрация DNS серверов по протоколу (UDP/DoT/DoH/DoQ)

This commit is contained in:
Dushnilin
2026-07-31 18:41:58 +03:00
parent ac1ff6bbb7
commit bc04794b73
5 changed files with 186 additions and 19 deletions
+35 -8
View File
@@ -42,14 +42,41 @@ export const DOMAIN_LIST_OPTIONS = {
cloudfront: 'CloudFront ASN',
};
export const DNS_SERVER_OPTIONS = {
'1.1.1.1': '1.1.1.1 (Cloudflare)',
'8.8.8.8': '8.8.8.8 (Google)',
'9.9.9.9': '9.9.9.9 (Quad9)',
'dns.adguard-dns.com': 'dns.adguard-dns.com (AdGuard Default)',
'unfiltered.adguard-dns.com':
'unfiltered.adguard-dns.com (AdGuard Unfiltered)',
'family.adguard-dns.com': 'family.adguard-dns.com (AdGuard Family)',
export const DNS_SERVERS_BY_PROTOCOL: Record<string, Record<string, string>> = {
udp: {
'1.1.1.1': '1.1.1.1 (Cloudflare)',
'8.8.8.8': '8.8.8.8 (Google)',
'9.9.9.9': '9.9.9.9 (Quad9)',
'77.88.8.8': '77.88.8.8 (Yandex)',
'94.140.14.14': '94.140.14.14 (AdGuard)',
'185.222.222.222': '185.222.222.222 (DNS.SB)',
'194.242.2.2': '194.242.2.2 (Mullvad)',
},
doh: {
'https://cloudflare-dns.com/dns-query': 'Cloudflare',
'https://dns.google/dns-query': 'Google',
'https://dns.quad9.net/dns-query': 'Quad9',
'https://dns.adguard.com/dns-query': 'AdGuard',
'https://dns.mullvad.net/dns-query': 'Mullvad',
'https://dns.nextdns.io/dns-query': 'NextDNS',
'https://doh.cleanbrowsing.org/doh/family-filter/': 'CleanBrowsing Family',
},
dot: {
'1.1.1.1': '1.1.1.1 (Cloudflare)',
'dns.google': 'Google',
'dns.quad9.net': 'Quad9',
'dns.adguard.com': 'AdGuard',
'dns.mullvad.net': 'Mullvad',
'dns.nextdns.io': 'NextDNS',
'common.dot.dns.yandex.net': 'Yandex',
},
doq: {
'1.1.1.1:784': 'Cloudflare',
'dns.google:784': 'Google',
'dns.adguard.com:785': 'AdGuard',
'dns.mullvad.net:784': 'Mullvad',
'dns.nextdns.io:784': 'NextDNS',
},
};
export const BOOTSTRAP_DNS_SERVER_OPTIONS = {
'77.88.8.8': '77.88.8.8 (Yandex DNS)',
+1 -1
View File
@@ -33,7 +33,7 @@ export { UpdatesTab } from './tachyon/tabs/updates';
export {
BOOTSTRAP_DNS_SERVER_OPTIONS,
DEFAULT_LATENCY_TEST_URL,
DNS_SERVER_OPTIONS,
DNS_SERVERS_BY_PROTOCOL,
DOMAIN_LIST_OPTIONS,
LATENCY_TEST_URL_OPTIONS,
TACHYON_ACTION_PROVIDERS_AVAILABILITY_EVENT,
@@ -1118,13 +1118,41 @@ var DOMAIN_LIST_OPTIONS = {
digitalocean: "Digital Ocean ASN",
cloudfront: "CloudFront ASN"
};
var DNS_SERVER_OPTIONS = {
"1.1.1.1": "1.1.1.1 (Cloudflare)",
"8.8.8.8": "8.8.8.8 (Google)",
"9.9.9.9": "9.9.9.9 (Quad9)",
"dns.adguard-dns.com": "dns.adguard-dns.com (AdGuard Default)",
"unfiltered.adguard-dns.com": "unfiltered.adguard-dns.com (AdGuard Unfiltered)",
"family.adguard-dns.com": "family.adguard-dns.com (AdGuard Family)"
var DNS_SERVERS_BY_PROTOCOL = {
udp: {
"1.1.1.1": "1.1.1.1 (Cloudflare)",
"8.8.8.8": "8.8.8.8 (Google)",
"9.9.9.9": "9.9.9.9 (Quad9)",
"77.88.8.8": "77.88.8.8 (Yandex)",
"94.140.14.14": "94.140.14.14 (AdGuard)",
"185.222.222.222": "185.222.222.222 (DNS.SB)",
"194.242.2.2": "194.242.2.2 (Mullvad)"
},
doh: {
"https://cloudflare-dns.com/dns-query": "Cloudflare",
"https://dns.google/dns-query": "Google",
"https://dns.quad9.net/dns-query": "Quad9",
"https://dns.adguard.com/dns-query": "AdGuard",
"https://dns.mullvad.net/dns-query": "Mullvad",
"https://dns.nextdns.io/dns-query": "NextDNS",
"https://doh.cleanbrowsing.org/doh/family-filter/": "CleanBrowsing Family"
},
dot: {
"1.1.1.1": "1.1.1.1 (Cloudflare)",
"dns.google": "Google",
"dns.quad9.net": "Quad9",
"dns.adguard.com": "AdGuard",
"dns.mullvad.net": "Mullvad",
"dns.nextdns.io": "NextDNS",
"common.dot.dns.yandex.net": "Yandex"
},
doq: {
"1.1.1.1:784": "Cloudflare",
"dns.google:784": "Google",
"dns.adguard.com:785": "AdGuard",
"dns.mullvad.net:784": "Mullvad",
"dns.nextdns.io:784": "NextDNS"
}
};
var BOOTSTRAP_DNS_SERVER_OPTIONS = {
"77.88.8.8": "77.88.8.8 (Yandex DNS)",
@@ -14933,7 +14961,7 @@ if (typeof structuredClone !== "function")
return baseclass.extend({
BOOTSTRAP_DNS_SERVER_OPTIONS,
DEFAULT_LATENCY_TEST_URL,
DNS_SERVER_OPTIONS,
DNS_SERVERS_BY_PROTOCOL,
DOMAIN_LIST_OPTIONS,
DashboardTab,
DiagnosticTab,
@@ -1778,6 +1778,7 @@ function dnsTypeChoices() {
return [
{ value: "doh", label: _("DNS over HTTPS (DoH)") },
{ value: "dot", label: _("DNS over TLS (DoT)") },
{ value: "doq", label: _("DNS over QUIC (DoQ)") },
{ value: "udp", label: "UDP" },
];
}
@@ -7244,6 +7245,7 @@ function createSectionContent(section) {
o.default = "udp";
o.rmempty = false;
o.modalonly = true;
const perRuleDnsTypeOption = o;
o = section.taboption(
"settings",
@@ -7254,7 +7256,17 @@ function createSectionContent(section) {
);
o.depends("action", "dns");
o.modalonly = true;
configureDnsList(o, main.DNS_SERVER_OPTIONS, "8.8.8.8");
configureLiveDynamicListChoices(o, (section_id) => {
const dnsType = uci.get(UCI_PACKAGE, section_id, "dns_type") || "udp";
const servers = main.DNS_SERVERS_BY_PROTOCOL[dnsType] || main.DNS_SERVERS_BY_PROTOCOL.udp;
return Object.entries(servers).map(([value, label]) => ({ value, label: _(label) }));
});
perRuleDnsTypeOption.onchange = function (_ev, section_id) {
const refreshers = dashboardFilterChoiceRefreshers.get(section_id);
if (refreshers) {
refreshers.forEach((fn) => fn());
}
};
o = section.taboption(
"settings",
@@ -138,6 +138,93 @@ function configureDnsFailoverVisibility(option, dnsOption, bootstrapOption) {
};
}
function refreshOptionChoices(option, choices) {
delete option.keylist;
delete option.vallist;
(choices || []).forEach((choice) => {
if (typeof choice === "object") {
option.value(choice.value, choice.label);
} else {
option.value(choice);
}
});
}
function getDnsServerChoices(dnsType) {
const servers = main.DNS_SERVERS_BY_PROTOCOL[dnsType] || main.DNS_SERVERS_BY_PROTOCOL.udp;
return Object.entries(servers).map(([value, label]) => ({ value, label: _(label) }));
}
function configureDnsDynamicList(option, getChoices, defaultValue) {
option.default = [defaultValue];
option.rmempty = false;
option.validate = function (_section_id, value) {
const normalized = `${value || ""}`.trim();
if (!normalized) {
return optionListValues(option, _section_id).length > 0
? true
: _("Add at least one DNS server");
}
const validation = main.validateDNS(normalized);
return validation.valid ? true : validation.message;
};
option.renderWidget = function (section_id, _option_index, cfgvalue) {
const values = L.toArray(cfgvalue != null ? cfgvalue : this.default);
const choices = getChoices(section_id, values);
const labels = {};
choices.forEach((choice) => {
labels[choice.value] = choice.label;
});
refreshOptionChoices(this, choices);
let choiceSignature = JSON.stringify(
choices.map((choice) => [choice.value, choice.label]),
);
const widget = new ui.DynamicList(values, labels, {
id: this.cbid(section_id),
sort: this.keylist,
optional: this.optional || this.rmempty,
datatype: this.datatype,
placeholder: this.placeholder,
validate: L.bind(this.validate, this, section_id),
disabled: this.readonly != null ? this.readonly : this.map.readonly,
});
const node = widget.render();
const refreshChoices = () => {
if (!node.isConnected) return false;
const currentValues = widget.getValue();
const currentChoices = getChoices(section_id, currentValues);
const currentLabels = {};
currentChoices.forEach((choice) => {
currentLabels[choice.value] = choice.label;
});
const currentSignature = JSON.stringify(
currentChoices.map((choice) => [choice.value, choice.label]),
);
if (currentSignature === choiceSignature) return;
choiceSignature = currentSignature;
refreshOptionChoices(this, currentChoices);
widget.choices = currentLabels;
widget.clearChoices();
widget.addChoices(
currentChoices.map((choice) => choice.value),
currentLabels,
);
};
const refreshBeforeOpening = (event) => {
if (event.target && event.target.closest(".add-item")) refreshChoices();
};
node.addEventListener("mousedown", refreshBeforeOpening, true);
node.addEventListener("focusin", refreshBeforeOpening, true);
if (!settingsDnsDynamicRefreshers.has(section_id)) {
settingsDnsDynamicRefreshers.set(section_id, new Set());
}
settingsDnsDynamicRefreshers.get(section_id).add(refreshChoices);
return node;
};
}
const settingsDnsDynamicRefreshers = new Map();
function configureDnsDuration(
option,
defaultValue,
@@ -849,10 +936,13 @@ function createSettingsContent(section, capabilities) {
);
o.value("doh", _("DNS over HTTPS (DoH)"));
o.value("dot", _("DNS over TLS (DoT)"));
o.value("doq", _("DNS over QUIC (DoQ)"));
o.value("udp", _("UDP (Unprotected DNS)"));
o.default = "udp";
o.rmempty = false;
const dnsTypeOption = o;
const dnsOption = section.option(
form.DynamicList,
"dns_server",
@@ -861,7 +951,10 @@ function createSettingsContent(section, capabilities) {
"Main DNS server. If multiple servers are selected, a timeout switches to a backup.",
),
);
configureDnsList(dnsOption, main.DNS_SERVER_OPTIONS, "77.88.8.8");
configureDnsDynamicList(dnsOption, (section_id) => {
const dnsType = uci.get(UCI_PACKAGE, "settings", "dns_type") || "udp";
return getDnsServerChoices(dnsType);
}, "77.88.8.8");
const bootstrapOption = section.option(
form.DynamicList,
@@ -877,6 +970,13 @@ function createSettingsContent(section, capabilities) {
"77.88.8.8",
);
dnsTypeOption.onchange = function (_ev, section_id, value) {
const refreshers = settingsDnsDynamicRefreshers.get(section_id);
if (refreshers) {
refreshers.forEach((fn) => fn());
}
};
o = section.option(
form.Flag,
"fallback_wan_main",