feat(dns): upstream mode (sequential/parallel) & explicit fallback DNS servers, bump 1.3.13

This commit is contained in:
Dushnilin
2026-09-01 20:49:21 +03:00
parent f55f37f804
commit 8b3c3e365f
16 changed files with 12908 additions and 10284 deletions
+6
View File
@@ -1288,6 +1288,12 @@ const dictionary = {
"Component versions and update manager": "Версии компонентов и менеджер обновлений",
"Core configuration and preferences": "Основные настройки и параметры",
"Remote management Telegram daemon": "Демон Telegram для удаленного управления",
"Upstream Mode": "Режим DNS Upstream",
"Sequential: failover to the next server after N failures. Parallel: aggressive failover — switches in seconds (1 failure = switch), best if your ISP blocks DoH/DoT.": "Последовательный: переключение на следующий сервер после N сбоев. Параллельный: быстрый failover — переключается за секунды (1 сбой = переключение), оптимально при блокировках DoH/DoT провайдером.",
"Sequential (one active at a time)": "Последовательный (один активный)",
"Parallel (fast failover, ≤3 s)": "Параллельный (быстрый failover, ≤3 с)",
"Fallback DNS Servers": "Резервные DNS серверы",
"Plain-UDP DNS servers used as a last resort when all primary DNS fail. These are always unencrypted (no DoH/DoT). Example: 1.1.1.1, 8.8.8.8. Unlike the ISP fallback toggle, these are your own chosen servers.": "Обычные UDP DNS-серверы, используемые как резерв при недоступности всех основных DNS. Всегда работают без шифрования (без DoH/DoT). Пример: 1.1.1.1, 8.8.8.8. В отличие от аварийного DNS провайдера, это выбранные вами серверы.",
};
function escapePoString(str) {
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+89 -65
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: TACHYON\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-01 10:48+0300\n"
"PO-Revision-Date: 2026-09-01 10:48+0300\n"
"POT-Creation-Date: 2026-09-01 20:47+0300\n"
"PO-Revision-Date: 2026-09-01 20:47+0300\n"
"Last-Translator: Dushnilin\n"
"Language-Team: none\n"
"Language: ru\n"
@@ -146,6 +146,9 @@ msgstr "Принимать внешние прокси-подключения и
msgid "Accept routes"
msgstr "Принимать маршруты"
msgid "Access schedule and screen time limits"
msgstr "Расписание доступа и лимиты времени"
msgid "Action"
msgstr "Действие"
@@ -392,6 +395,9 @@ msgstr "Alter ID"
msgid "Always block these domains for all devices in this profile. Enter one domain per line or use category preset buttons."
msgstr "Всегда блокировать эти домены для всех устройств в этом профиле. Вводите по одному домену на строку или используйте кнопки категорий."
msgid "Always visible"
msgstr "Всегда включено"
msgid "AmneziaWG config loaded successfully!"
msgstr "Конфигурация AmneziaWG успешно загружена!"
@@ -821,6 +827,12 @@ msgstr "Очистить"
msgid "Clear DNS cache"
msgstr "Очистить кэш DNS"
msgid "Click to hide"
msgstr ""
msgid "Click to show"
msgstr ""
msgid "Client Address"
msgstr "Адрес клиента"
@@ -893,6 +905,9 @@ msgstr "Доступно обновление компонента"
msgid "Component update check interval"
msgstr "Интервал проверки обновлений компонентов"
msgid "Component versions and update manager"
msgstr "Версии компонентов и менеджер обновлений"
msgid "Components"
msgstr "Компоненты"
@@ -965,6 +980,9 @@ msgstr "Скопировать ссылку прокси"
msgid "Copy Support Report"
msgstr "Скопировать отчет для поддержки"
msgid "Core configuration and preferences"
msgstr "Основные настройки и параметры"
msgid "Could not compare FakeIP and control public IPs"
msgstr "Не удалось сравнить публичные IP FakeIP и контрольной проверки"
@@ -1022,6 +1040,9 @@ msgstr "Пользовательские конфигурации outbound в ф
msgid "Custom strategy added!"
msgstr "Пользовательская стратегия добавлена!"
msgid "Customize the order and visibility of tabs in the main navigation menu. Use the arrows to reorder and checkboxes to show or hide tabs."
msgstr "Настройте порядок и видимость вкладок в главном меню навигации. Используйте стрелки для перемещения и галочки для показа или скрытия."
msgid "Daily Quota"
msgstr "Дневная квота"
@@ -1052,6 +1073,9 @@ msgstr "По умолчанию (WAN / стандартный смешанный
msgid "Default address for checking server availability and latency. URLTest uses its own address."
msgstr "Адрес по умолчанию для проверки доступности и задержки сервера. URLTest использует свой собственный адрес."
msgid "Default Tab on Startup"
msgstr "Вкладка по умолчанию при входе"
msgid "Delay in milliseconds before reloading Tachyon after interface UP"
msgstr "Задержка в миллисекундах перед перезагрузкой Tachyon после поднятия интерфейса"
@@ -1553,6 +1577,9 @@ msgstr "Истекает"
msgid "Expose this section as a local HTTP+SOCKS proxy"
msgstr "Предоставить эту секцию как локальный HTTP+SOCKS прокси"
msgid "External inbound proxy servers"
msgstr "Внешние входящие прокси-серверы"
msgid "External nfqws config files bypass Tachyon queue management and explicit validation."
msgstr "Внешние файлы конфигурации nfqws обходят управление очередями Tachyon и явную проверку."
@@ -1709,6 +1736,9 @@ msgstr "FakeIP работает; сравнение публичных IP не
msgid "FakeTLS host"
msgstr "FakeTLS host"
msgid "Fallback DNS Servers"
msgstr "Резервные DNS серверы"
msgid "Fallback on unknown DC"
msgstr "Fallback при неизвестном DC"
@@ -1721,6 +1751,9 @@ msgstr "Семейный профиль"
msgid "Family Profiles"
msgstr "Семейные профили"
msgid "Family profiles and device grouping"
msgstr "Семейные профили и группировка устройств"
msgid "Faster DNS health monitoring than the default cycle. Switches bootstrap DNS on failure."
msgstr "Более быстрый мониторинг состояния DNS, чем стандартный цикл. Переключает bootstrap DNS при сбое."
@@ -2420,6 +2453,9 @@ msgstr "Казахстан (kz)"
msgid "Keep Priority before latency-sorted servers"
msgstr "Сохранять приоритет перед серверами, отсортированными по задержке"
msgid "Keep the previous working binary/package on the router flash for instant offline rollback if needed. Consumes extra storage."
msgstr "Сохранять предыдущую рабочую версию компонента в памяти роутера для быстрого локального отката. Занимает дополнительное место на flash."
msgid "Keep URLTest before latency-sorted servers"
msgstr "Сохранять URLTest перед серверами, отсортированными по задержке"
@@ -2537,6 +2573,9 @@ msgstr "Основной DNS"
msgid "Main DNS server. If multiple servers are selected, a timeout switches to a backup."
msgstr "Основной DNS-сервер. Если выбрано несколько серверов, при таймауте происходит переключение на резервный."
msgid "Main overview and status widgets"
msgstr "Главный обзор и виджеты статуса"
msgid "Manual"
msgstr "Вручную"
@@ -2579,6 +2618,9 @@ msgstr "Максимальное время ожидания разрешени
msgid "Memory Usage"
msgstr "Использование памяти"
msgid "Menu & Navigation"
msgstr "Меню и навигация"
msgid "Messengers"
msgstr "Мессенджеры"
@@ -2627,6 +2669,12 @@ msgstr "Мониторинг"
msgid "Monitors sing-box reconnect frequency. Alerts if too many reconnects per hour."
msgstr "Отслеживает частоту переподключений sing-box. Оповещает при слишком частых переподключениях."
msgid "Move Down"
msgstr "Переместить ниже"
msgid "Move Up"
msgstr "Переместить выше"
msgid "MTU"
msgstr "MTU"
@@ -2975,6 +3023,9 @@ msgstr "Защита от утечек P2P"
msgid "Padding"
msgstr "Padding"
msgid "Parallel (fast failover, ≤3 s)"
msgstr "Параллельный (быстрый failover, ≤3 с)"
msgid "Parental Control"
msgstr "Родительский контроль"
@@ -3071,6 +3122,9 @@ msgstr "Закрепить на дашборде"
msgid "Pin P2P isolation to the torrent client's own ports, comma separated. Each entry is \"proto\", \"proto:port\" or \"proto:port-port\" (tcp/udp). Without a port the whole protocol is routed direct."
msgstr "Закрепить изоляцию P2P за портами торрент-клиента, через запятую. Каждая запись — \"протокол\", \"протокол:порт\" или \"протокол:порт-порт\" (tcp/udp). Без порта весь протокол уходит напрямую."
msgid "Plain-UDP DNS servers used as a last resort when all primary DNS fail. These are always unencrypted (no DoH/DoT). Example: 1.1.1.1, 8.8.8.8. Unlike the ISP fallback toggle, these are your own chosen servers."
msgstr "Обычные UDP DNS-серверы, используемые как резерв при недоступности всех основных DNS. Всегда работают без шифрования (без DoH/DoT). Пример: 1.1.1.1, 8.8.8.8. В отличие от аварийного DNS провайдера, это выбранные вами серверы."
msgid "Please specify strategy name and arguments"
msgstr "Пожалуйста, укажите имя стратегии и аргументы"
@@ -3299,6 +3353,9 @@ msgstr "Перезагрузка..."
msgid "Remote Hosts Lists"
msgstr "Удалённые списки Hosts"
msgid "Remote management Telegram daemon"
msgstr "Демон Telegram для удаленного управления"
msgid "Removal completed successfully!"
msgstr "Удаление успешно завершено!"
@@ -3422,6 +3479,9 @@ msgstr "Продолжить обновление"
msgid "Return NXDOMAIN for blocked domains (recommended). When enabled, blocked domains are not resolved at all. When disabled, only direct connections are rejected."
msgstr "Возвращать NXDOMAIN для заблокированных доменов (рекомендуется). При включении заблокированные домены вообще не резолвятся. При выключении отклоняются только прямые соединения."
msgid "Rollback"
msgstr "Откатить"
msgid "Route"
msgstr "Маршрут"
@@ -3452,6 +3512,9 @@ msgstr "Правила маршрутизации"
msgid "Routing section"
msgstr "Секция маршрутизации"
msgid "Routing sections, rules and strategies"
msgstr "Секции маршрутизации, правила и стратегии"
msgid "Rule"
msgstr "Правило"
@@ -3530,6 +3593,9 @@ msgstr "Сохранить"
msgid "Save a snapshot of the working config and restore it later if something breaks."
msgstr "Сохранить снапшот рабочей конфигурации, чтобы восстановить её позже, если что-то сломается."
msgid "Save component backup before update"
msgstr "Сохранять резервную копию перед обновлением"
msgid "Save Snapshot"
msgstr "Сохранить снапшот"
@@ -3674,6 +3740,9 @@ msgstr "Выберите, отключать ли цель в период ра
msgid "Select which components are permitted to update automatically"
msgstr "Выберите, какие компоненты разрешено обновлять автоматически"
msgid "Select which tab opens automatically when entering Tachyon."
msgstr "Выберите, какая вкладка открывается автоматически при входе в Tachyon."
msgid "Selected"
msgstr "Выбран"
@@ -3713,6 +3782,12 @@ msgstr "Отправлять уведомления родителям в Telegr
msgid "Sequence Overlaps (seqovl)"
msgstr "Перекрытия последовательностей (seqovl)"
msgid "Sequential (one active at a time)"
msgstr "Последовательный (один активный)"
msgid "Sequential: failover to the next server after N failures. Parallel: aggressive failover — switches in seconds (1 failure = switch), best if your ISP blocks DoH/DoT."
msgstr "Последовательный: переключение на следующий сервер после N сбоев. Параллельный: быстрый failover — переключается за секунды (1 сбой = переключение), оптимально при блокировках DoH/DoT провайдером."
msgid "Server"
msgstr "Сервер"
@@ -4103,12 +4178,18 @@ msgstr "Вернуть автовыбор"
msgid "Sync System Time (NTP)"
msgstr "Синхронизировать время (NTP)"
msgid "System diagnostics, AI Doctor and logs"
msgstr "Системная диагностика, ИИ-Доктор и логи"
msgid "System info"
msgstr "Системная информация"
msgid "System information"
msgstr "Системная информация"
msgid "Tab Order & Visibility"
msgstr "Порядок и видимость вкладок"
msgid "Table exist"
msgstr "Таблица существует"
@@ -4400,6 +4481,9 @@ msgstr "Трафик от этих IP-адресов никогда не буд
msgid "Traffic of specified protocols (e.g. BitTorrent, QUIC, STUN) will bypass this section."
msgstr "Трафик указанных протоколов (например BitTorrent, QUIC, STUN) будет обходить эту секцию."
msgid "Traffic statistics and active connections"
msgstr "Статистика трафика и активные соединения"
msgid "Traffic Total"
msgstr "Всего трафика"
@@ -4607,6 +4691,9 @@ msgstr "Исходящий"
msgid "Uploaded"
msgstr "Отдано"
msgid "Upstream Mode"
msgstr "Режим DNS Upstream"
msgid "URL must start with vless://, vmess://, ss://, trojan://, socks4://, socks4a://, socks5://, hysteria2://, hy2://, or tuic://"
msgstr "URL должен начинаться с vless://, vmess://, ss://, trojan://, socks4://, socks4a://, socks5://, hysteria2://, hy2:// или tuic://"
@@ -5387,24 +5474,6 @@ msgstr "Очистить историю"
msgid "Auto-applied best strategy"
msgstr "Лучшая стратегия автоматически применена"
msgid "Rollback"
msgstr "Откатить"
msgid "Save component backup before update"
msgstr "Сохранять резервную копию перед обновлением"
msgid "Keep the previous working binary/package on the router flash for instant offline rollback if needed. Consumes extra storage."
msgstr "Сохранять предыдущую рабочую версию компонента в памяти роутера для быстрого локального отката. Занимает дополнительное место на flash."
msgid "Menu & Navigation"
msgstr "Меню и навигация"
msgid "Default Tab on Startup"
msgstr "Вкладка по умолчанию при входе"
msgid "Select which tab opens automatically when entering Tachyon."
msgstr "Выберите, какая вкладка открывается автоматически при входе в Tachyon."
msgid "Show 'Dashboard' tab"
msgstr "Отображать вкладку «Главная»"
@@ -5458,48 +5527,3 @@ msgstr "Отображать вкладку «Telegram-бот»"
msgid "Display the Telegram bot settings and controls in the menu."
msgstr "Отображать настройки и управление Telegram-ботом в меню."
msgid "Tab Order & Visibility"
msgstr "Порядок и видимость вкладок"
msgid "Customize the order and visibility of tabs in the main navigation menu. Use the arrows to reorder and checkboxes to show or hide tabs."
msgstr "Настройте порядок и видимость вкладок в главном меню навигации. Используйте стрелки для перемещения и галочки для показа или скрытия."
msgid "Always visible"
msgstr "Всегда включено"
msgid "Move Up"
msgstr "Переместить выше"
msgid "Move Down"
msgstr "Переместить ниже"
msgid "Main overview and status widgets"
msgstr "Главный обзор и виджеты статуса"
msgid "Routing sections, rules and strategies"
msgstr "Секции маршрутизации, правила и стратегии"
msgid "External inbound proxy servers"
msgstr "Внешние входящие прокси-серверы"
msgid "Family profiles and device grouping"
msgstr "Семейные профили и группировка устройств"
msgid "Access schedule and screen time limits"
msgstr "Расписание доступа и лимиты времени"
msgid "Traffic statistics and active connections"
msgstr "Статистика трафика и активные соединения"
msgid "System diagnostics, AI Doctor and logs"
msgstr "Системная диагностика, ИИ-Доктор и логи"
msgid "Component versions and update manager"
msgstr "Версии компонентов и менеджер обновлений"
msgid "Core configuration and preferences"
msgstr "Основные настройки и параметры"
msgid "Remote management Telegram daemon"
msgstr "Демон Telegram для удаленного управления"
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "fe-app-tachyon",
"version": "1.3.12",
"version": "1.3.13",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "fe-app-tachyon",
"version": "1.3.12",
"version": "1.3.13",
"license": "MIT",
"dependencies": {
"@esbuild/win32-x64": "^0.28.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "fe-app-tachyon",
"version": "1.3.12",
"version": "1.3.13",
"license": "MIT",
"type": "module",
"scripts": {
File diff suppressed because it is too large Load Diff
@@ -127,15 +127,26 @@ function configureDnsList(option, choices, defaultValue) {
};
}
function configureDnsFailoverVisibility(option, dnsOption, bootstrapOption) {
function configureDnsFailoverVisibility(
option,
dnsOption,
bootstrapOption,
fallbackOption,
) {
option.depends("dns_server", "__tachyon_multiple_dns__");
option.depends("bootstrap_dns_server", "__tachyon_multiple_dns__");
option.retain = true;
option.checkDepends = function (section_id) {
return (
if (
optionListValues(dnsOption, section_id).length > 1 ||
optionListValues(bootstrapOption, section_id).length > 1
);
)
return true;
// Also show when explicit fallback servers are configured
if (fallbackOption) {
return optionListValues(fallbackOption, section_id).length > 0;
}
return false;
};
}
@@ -249,6 +260,7 @@ function configureDnsDuration(
defaultValue,
dnsOption,
bootstrapOption,
fallbackOption,
) {
option.default = defaultValue;
option.rmempty = false;
@@ -259,7 +271,12 @@ function configureDnsDuration(
}
return true;
};
configureDnsFailoverVisibility(option, dnsOption, bootstrapOption);
configureDnsFailoverVisibility(
option,
dnsOption,
bootstrapOption,
fallbackOption,
);
}
function createWatchdogStatusWidget() {
@@ -1328,6 +1345,22 @@ function createSettingsContent(section, capabilities) {
const dnsTypeOption = o;
o = section.taboption(
"dns",
form.ListValue,
"dns_upstream_mode",
_("Upstream Mode"),
_(
"Sequential: failover to the next server after N failures. Parallel: aggressive failover — switches in seconds (1 failure = switch), best if your ISP blocks DoH/DoT.",
),
);
o.value("sequential", _("Sequential (one active at a time)"));
o.value("parallel", _("Parallel (fast failover, ≤3 s)"));
o.default = "sequential";
o.rmempty = false;
const dnsModeOption = o;
const dnsOption = section.taboption(
"dns",
form.DynamicList,
@@ -1361,6 +1394,25 @@ function createSettingsContent(section, capabilities) {
"77.88.8.8",
);
const fallbackDnsOption = section.taboption(
"dns",
form.DynamicList,
"dns_fallback_server",
_("Fallback DNS Servers"),
_(
"Plain-UDP DNS servers used as a last resort when all primary DNS fail. These are always unencrypted (no DoH/DoT). Example: 1.1.1.1, 8.8.8.8. Unlike the ISP fallback toggle, these are your own chosen servers.",
),
);
configureDnsList(fallbackDnsOption, main.BOOTSTRAP_DNS_SERVER_OPTIONS, "");
fallbackDnsOption.rmempty = true;
fallbackDnsOption.default = [];
fallbackDnsOption.validate = function (_section_id, value) {
const normalized = `${value || ""}`.trim();
if (!normalized) return true; // allow empty list
const validation = main.validateDNS(normalized);
return validation.valid ? true : validation.message;
};
dnsTypeOption.onchange = function (_ev, section_id, value) {
const newType = value || "udp";
if (newType === settingsDnsDynamicState.dnsType) return;
@@ -1408,6 +1460,22 @@ function createSettingsContent(section, capabilities) {
);
o.default = o.disabled;
// Timing controls — hidden in parallel mode (auto-configured) and when only one server
function configureTimingVisibility(opt) {
opt.retain = true;
opt.checkDepends = function (section_id) {
// hide when parallel (auto-tuned) unless there are multiple servers
const mode =
uci.get(UCI_PACKAGE, "settings", "dns_upstream_mode") || "sequential";
if (mode === "parallel") return false;
return (
optionListValues(dnsOption, section_id).length > 1 ||
optionListValues(bootstrapOption, section_id).length > 1 ||
optionListValues(fallbackDnsOption, section_id).length > 0
);
};
}
o = section.taboption(
"dns",
form.Value,
@@ -1415,7 +1483,8 @@ function createSettingsContent(section, capabilities) {
_("DNS Check Interval"),
_("How often to check the active DNS servers."),
);
configureDnsDuration(o, "10s", dnsOption, bootstrapOption);
configureDnsDuration(o, "10s", dnsOption, bootstrapOption, fallbackDnsOption);
configureTimingVisibility(o);
o = section.taboption(
"dns",
@@ -1424,7 +1493,8 @@ function createSettingsContent(section, capabilities) {
_("Higher-priority DNS Check"),
_("How often to check whether a higher-priority DNS server has recovered."),
);
configureDnsDuration(o, "60s", dnsOption, bootstrapOption);
configureDnsDuration(o, "60s", dnsOption, bootstrapOption, fallbackDnsOption);
configureTimingVisibility(o);
o = section.taboption(
"dns",
@@ -1435,7 +1505,8 @@ function createSettingsContent(section, capabilities) {
"Maximum time to wait for example.com to resolve during a DNS health check.",
),
);
configureDnsDuration(o, "2s", dnsOption, bootstrapOption);
configureDnsDuration(o, "2s", dnsOption, bootstrapOption, fallbackDnsOption);
configureTimingVisibility(o);
o = section.taboption(
"dns",
@@ -1449,7 +1520,13 @@ function createSettingsContent(section, capabilities) {
o.default = "3";
o.rmempty = false;
o.datatype = "range(1, 10)";
configureDnsFailoverVisibility(o, dnsOption, bootstrapOption);
configureDnsFailoverVisibility(
o,
dnsOption,
bootstrapOption,
fallbackDnsOption,
);
configureTimingVisibility(o);
o = section.taboption(
"dns",
@@ -1463,7 +1540,13 @@ function createSettingsContent(section, capabilities) {
o.default = "3";
o.rmempty = false;
o.datatype = "range(1, 10)";
configureDnsFailoverVisibility(o, dnsOption, bootstrapOption);
configureDnsFailoverVisibility(
o,
dnsOption,
bootstrapOption,
fallbackDnsOption,
);
configureTimingVisibility(o);
o = section.taboption(
"dns",
+89 -65
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: TACHYON\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-01 10:48+0300\n"
"PO-Revision-Date: 2026-09-01 10:48+0300\n"
"POT-Creation-Date: 2026-09-01 20:47+0300\n"
"PO-Revision-Date: 2026-09-01 20:47+0300\n"
"Last-Translator: Dushnilin\n"
"Language-Team: none\n"
"Language: ru\n"
@@ -146,6 +146,9 @@ msgstr "Принимать внешние прокси-подключения и
msgid "Accept routes"
msgstr "Принимать маршруты"
msgid "Access schedule and screen time limits"
msgstr "Расписание доступа и лимиты времени"
msgid "Action"
msgstr "Действие"
@@ -392,6 +395,9 @@ msgstr "Alter ID"
msgid "Always block these domains for all devices in this profile. Enter one domain per line or use category preset buttons."
msgstr "Всегда блокировать эти домены для всех устройств в этом профиле. Вводите по одному домену на строку или используйте кнопки категорий."
msgid "Always visible"
msgstr "Всегда включено"
msgid "AmneziaWG config loaded successfully!"
msgstr "Конфигурация AmneziaWG успешно загружена!"
@@ -821,6 +827,12 @@ msgstr "Очистить"
msgid "Clear DNS cache"
msgstr "Очистить кэш DNS"
msgid "Click to hide"
msgstr ""
msgid "Click to show"
msgstr ""
msgid "Client Address"
msgstr "Адрес клиента"
@@ -893,6 +905,9 @@ msgstr "Доступно обновление компонента"
msgid "Component update check interval"
msgstr "Интервал проверки обновлений компонентов"
msgid "Component versions and update manager"
msgstr "Версии компонентов и менеджер обновлений"
msgid "Components"
msgstr "Компоненты"
@@ -965,6 +980,9 @@ msgstr "Скопировать ссылку прокси"
msgid "Copy Support Report"
msgstr "Скопировать отчет для поддержки"
msgid "Core configuration and preferences"
msgstr "Основные настройки и параметры"
msgid "Could not compare FakeIP and control public IPs"
msgstr "Не удалось сравнить публичные IP FakeIP и контрольной проверки"
@@ -1022,6 +1040,9 @@ msgstr "Пользовательские конфигурации outbound в ф
msgid "Custom strategy added!"
msgstr "Пользовательская стратегия добавлена!"
msgid "Customize the order and visibility of tabs in the main navigation menu. Use the arrows to reorder and checkboxes to show or hide tabs."
msgstr "Настройте порядок и видимость вкладок в главном меню навигации. Используйте стрелки для перемещения и галочки для показа или скрытия."
msgid "Daily Quota"
msgstr "Дневная квота"
@@ -1052,6 +1073,9 @@ msgstr "По умолчанию (WAN / стандартный смешанный
msgid "Default address for checking server availability and latency. URLTest uses its own address."
msgstr "Адрес по умолчанию для проверки доступности и задержки сервера. URLTest использует свой собственный адрес."
msgid "Default Tab on Startup"
msgstr "Вкладка по умолчанию при входе"
msgid "Delay in milliseconds before reloading Tachyon after interface UP"
msgstr "Задержка в миллисекундах перед перезагрузкой Tachyon после поднятия интерфейса"
@@ -1553,6 +1577,9 @@ msgstr "Истекает"
msgid "Expose this section as a local HTTP+SOCKS proxy"
msgstr "Предоставить эту секцию как локальный HTTP+SOCKS прокси"
msgid "External inbound proxy servers"
msgstr "Внешние входящие прокси-серверы"
msgid "External nfqws config files bypass Tachyon queue management and explicit validation."
msgstr "Внешние файлы конфигурации nfqws обходят управление очередями Tachyon и явную проверку."
@@ -1709,6 +1736,9 @@ msgstr "FakeIP работает; сравнение публичных IP не
msgid "FakeTLS host"
msgstr "FakeTLS host"
msgid "Fallback DNS Servers"
msgstr "Резервные DNS серверы"
msgid "Fallback on unknown DC"
msgstr "Fallback при неизвестном DC"
@@ -1721,6 +1751,9 @@ msgstr "Семейный профиль"
msgid "Family Profiles"
msgstr "Семейные профили"
msgid "Family profiles and device grouping"
msgstr "Семейные профили и группировка устройств"
msgid "Faster DNS health monitoring than the default cycle. Switches bootstrap DNS on failure."
msgstr "Более быстрый мониторинг состояния DNS, чем стандартный цикл. Переключает bootstrap DNS при сбое."
@@ -2420,6 +2453,9 @@ msgstr "Казахстан (kz)"
msgid "Keep Priority before latency-sorted servers"
msgstr "Сохранять приоритет перед серверами, отсортированными по задержке"
msgid "Keep the previous working binary/package on the router flash for instant offline rollback if needed. Consumes extra storage."
msgstr "Сохранять предыдущую рабочую версию компонента в памяти роутера для быстрого локального отката. Занимает дополнительное место на flash."
msgid "Keep URLTest before latency-sorted servers"
msgstr "Сохранять URLTest перед серверами, отсортированными по задержке"
@@ -2537,6 +2573,9 @@ msgstr "Основной DNS"
msgid "Main DNS server. If multiple servers are selected, a timeout switches to a backup."
msgstr "Основной DNS-сервер. Если выбрано несколько серверов, при таймауте происходит переключение на резервный."
msgid "Main overview and status widgets"
msgstr "Главный обзор и виджеты статуса"
msgid "Manual"
msgstr "Вручную"
@@ -2579,6 +2618,9 @@ msgstr "Максимальное время ожидания разрешени
msgid "Memory Usage"
msgstr "Использование памяти"
msgid "Menu & Navigation"
msgstr "Меню и навигация"
msgid "Messengers"
msgstr "Мессенджеры"
@@ -2627,6 +2669,12 @@ msgstr "Мониторинг"
msgid "Monitors sing-box reconnect frequency. Alerts if too many reconnects per hour."
msgstr "Отслеживает частоту переподключений sing-box. Оповещает при слишком частых переподключениях."
msgid "Move Down"
msgstr "Переместить ниже"
msgid "Move Up"
msgstr "Переместить выше"
msgid "MTU"
msgstr "MTU"
@@ -2975,6 +3023,9 @@ msgstr "Защита от утечек P2P"
msgid "Padding"
msgstr "Padding"
msgid "Parallel (fast failover, ≤3 s)"
msgstr "Параллельный (быстрый failover, ≤3 с)"
msgid "Parental Control"
msgstr "Родительский контроль"
@@ -3071,6 +3122,9 @@ msgstr "Закрепить на дашборде"
msgid "Pin P2P isolation to the torrent client's own ports, comma separated. Each entry is \"proto\", \"proto:port\" or \"proto:port-port\" (tcp/udp). Without a port the whole protocol is routed direct."
msgstr "Закрепить изоляцию P2P за портами торрент-клиента, через запятую. Каждая запись — \"протокол\", \"протокол:порт\" или \"протокол:порт-порт\" (tcp/udp). Без порта весь протокол уходит напрямую."
msgid "Plain-UDP DNS servers used as a last resort when all primary DNS fail. These are always unencrypted (no DoH/DoT). Example: 1.1.1.1, 8.8.8.8. Unlike the ISP fallback toggle, these are your own chosen servers."
msgstr "Обычные UDP DNS-серверы, используемые как резерв при недоступности всех основных DNS. Всегда работают без шифрования (без DoH/DoT). Пример: 1.1.1.1, 8.8.8.8. В отличие от аварийного DNS провайдера, это выбранные вами серверы."
msgid "Please specify strategy name and arguments"
msgstr "Пожалуйста, укажите имя стратегии и аргументы"
@@ -3299,6 +3353,9 @@ msgstr "Перезагрузка..."
msgid "Remote Hosts Lists"
msgstr "Удалённые списки Hosts"
msgid "Remote management Telegram daemon"
msgstr "Демон Telegram для удаленного управления"
msgid "Removal completed successfully!"
msgstr "Удаление успешно завершено!"
@@ -3422,6 +3479,9 @@ msgstr "Продолжить обновление"
msgid "Return NXDOMAIN for blocked domains (recommended). When enabled, blocked domains are not resolved at all. When disabled, only direct connections are rejected."
msgstr "Возвращать NXDOMAIN для заблокированных доменов (рекомендуется). При включении заблокированные домены вообще не резолвятся. При выключении отклоняются только прямые соединения."
msgid "Rollback"
msgstr "Откатить"
msgid "Route"
msgstr "Маршрут"
@@ -3452,6 +3512,9 @@ msgstr "Правила маршрутизации"
msgid "Routing section"
msgstr "Секция маршрутизации"
msgid "Routing sections, rules and strategies"
msgstr "Секции маршрутизации, правила и стратегии"
msgid "Rule"
msgstr "Правило"
@@ -3530,6 +3593,9 @@ msgstr "Сохранить"
msgid "Save a snapshot of the working config and restore it later if something breaks."
msgstr "Сохранить снапшот рабочей конфигурации, чтобы восстановить её позже, если что-то сломается."
msgid "Save component backup before update"
msgstr "Сохранять резервную копию перед обновлением"
msgid "Save Snapshot"
msgstr "Сохранить снапшот"
@@ -3674,6 +3740,9 @@ msgstr "Выберите, отключать ли цель в период ра
msgid "Select which components are permitted to update automatically"
msgstr "Выберите, какие компоненты разрешено обновлять автоматически"
msgid "Select which tab opens automatically when entering Tachyon."
msgstr "Выберите, какая вкладка открывается автоматически при входе в Tachyon."
msgid "Selected"
msgstr "Выбран"
@@ -3713,6 +3782,12 @@ msgstr "Отправлять уведомления родителям в Telegr
msgid "Sequence Overlaps (seqovl)"
msgstr "Перекрытия последовательностей (seqovl)"
msgid "Sequential (one active at a time)"
msgstr "Последовательный (один активный)"
msgid "Sequential: failover to the next server after N failures. Parallel: aggressive failover — switches in seconds (1 failure = switch), best if your ISP blocks DoH/DoT."
msgstr "Последовательный: переключение на следующий сервер после N сбоев. Параллельный: быстрый failover — переключается за секунды (1 сбой = переключение), оптимально при блокировках DoH/DoT провайдером."
msgid "Server"
msgstr "Сервер"
@@ -4103,12 +4178,18 @@ msgstr "Вернуть автовыбор"
msgid "Sync System Time (NTP)"
msgstr "Синхронизировать время (NTP)"
msgid "System diagnostics, AI Doctor and logs"
msgstr "Системная диагностика, ИИ-Доктор и логи"
msgid "System info"
msgstr "Системная информация"
msgid "System information"
msgstr "Системная информация"
msgid "Tab Order & Visibility"
msgstr "Порядок и видимость вкладок"
msgid "Table exist"
msgstr "Таблица существует"
@@ -4400,6 +4481,9 @@ msgstr "Трафик от этих IP-адресов никогда не буд
msgid "Traffic of specified protocols (e.g. BitTorrent, QUIC, STUN) will bypass this section."
msgstr "Трафик указанных протоколов (например BitTorrent, QUIC, STUN) будет обходить эту секцию."
msgid "Traffic statistics and active connections"
msgstr "Статистика трафика и активные соединения"
msgid "Traffic Total"
msgstr "Всего трафика"
@@ -4607,6 +4691,9 @@ msgstr "Исходящий"
msgid "Uploaded"
msgstr "Отдано"
msgid "Upstream Mode"
msgstr "Режим DNS Upstream"
msgid "URL must start with vless://, vmess://, ss://, trojan://, socks4://, socks4a://, socks5://, hysteria2://, hy2://, or tuic://"
msgstr "URL должен начинаться с vless://, vmess://, ss://, trojan://, socks4://, socks4a://, socks5://, hysteria2://, hy2:// или tuic://"
@@ -5387,24 +5474,6 @@ msgstr "Очистить историю"
msgid "Auto-applied best strategy"
msgstr "Лучшая стратегия автоматически применена"
msgid "Rollback"
msgstr "Откатить"
msgid "Save component backup before update"
msgstr "Сохранять резервную копию перед обновлением"
msgid "Keep the previous working binary/package on the router flash for instant offline rollback if needed. Consumes extra storage."
msgstr "Сохранять предыдущую рабочую версию компонента в памяти роутера для быстрого локального отката. Занимает дополнительное место на flash."
msgid "Menu & Navigation"
msgstr "Меню и навигация"
msgid "Default Tab on Startup"
msgstr "Вкладка по умолчанию при входе"
msgid "Select which tab opens automatically when entering Tachyon."
msgstr "Выберите, какая вкладка открывается автоматически при входе в Tachyon."
msgid "Show 'Dashboard' tab"
msgstr "Отображать вкладку «Главная»"
@@ -5458,48 +5527,3 @@ msgstr "Отображать вкладку «Telegram-бот»"
msgid "Display the Telegram bot settings and controls in the menu."
msgstr "Отображать настройки и управление Telegram-ботом в меню."
msgid "Tab Order & Visibility"
msgstr "Порядок и видимость вкладок"
msgid "Customize the order and visibility of tabs in the main navigation menu. Use the arrows to reorder and checkboxes to show or hide tabs."
msgstr "Настройте порядок и видимость вкладок в главном меню навигации. Используйте стрелки для перемещения и галочки для показа или скрытия."
msgid "Always visible"
msgstr "Всегда включено"
msgid "Move Up"
msgstr "Переместить выше"
msgid "Move Down"
msgstr "Переместить ниже"
msgid "Main overview and status widgets"
msgstr "Главный обзор и виджеты статуса"
msgid "Routing sections, rules and strategies"
msgstr "Секции маршрутизации, правила и стратегии"
msgid "External inbound proxy servers"
msgstr "Внешние входящие прокси-серверы"
msgid "Family profiles and device grouping"
msgstr "Семейные профили и группировка устройств"
msgid "Access schedule and screen time limits"
msgstr "Расписание доступа и лимиты времени"
msgid "Traffic statistics and active connections"
msgstr "Статистика трафика и активные соединения"
msgid "System diagnostics, AI Doctor and logs"
msgstr "Системная диагностика, ИИ-Доктор и логи"
msgid "Component versions and update manager"
msgstr "Версии компонентов и менеджер обновлений"
msgid "Core configuration and preferences"
msgstr "Основные настройки и параметры"
msgid "Remote management Telegram daemon"
msgstr "Демон Telegram для удаленного управления"
File diff suppressed because it is too large Load Diff
+4
View File
@@ -6,6 +6,8 @@ let strings = {
// ─── Setting schema labels ───
dns_type: "DNS Type",
dns_server: "DNS Servers",
dns_upstream_mode: "DNS Upstream Mode",
dns_fallback_server: "Fallback DNS Servers",
bootstrap_dns_server: "Bootstrap DNS",
dns_strategy: "DNS Strategy",
dns_detour_enabled: "DNS Detour",
@@ -534,6 +536,8 @@ let strings = {
// ─── Setting schema labels ───
dns_type: "Тип DNS",
dns_server: "DNS Серверы",
dns_upstream_mode: "Режим DNS Upstream",
dns_fallback_server: "Резервные DNS Серверы",
bootstrap_dns_server: "Bootstrap DNS",
dns_strategy: "Стратегия DNS",
dns_detour_enabled: "DNS Detour",
+3
View File
@@ -1512,8 +1512,11 @@ function sing_box_signature_body(settings, sections, servers, mwan3_active) {
body = signature_add_value(body, "settings.dns_type", option(settings, "dns_type", "doh"));
body = signature_add_value(body, "settings.dns_strategy", option(settings, "dns_strategy", "prefer_ipv4"));
body = signature_add_value(body, "settings.dns_upstream_mode", option(settings, "dns_upstream_mode", "sequential"));
for (let value in list_option(settings, "dns_server", "77.88.8.8"))
body = signature_add_value(body, "settings.dns_server", value);
for (let value in list_option(settings, "dns_fallback_server"))
body = signature_add_value(body, "settings.dns_fallback_server", value);
for (let value in list_option(settings, "bootstrap_dns_server", "77.88.8.8"))
body = signature_add_value(body, "settings.bootstrap_dns_server", value);
body = signature_add_value(body, "settings.dns_check_interval", option(settings, "dns_check_interval", "10s"));
+29 -4
View File
@@ -41,6 +41,14 @@ function server_list(settings, key, fallback) {
if (value != "")
push(result, value);
}
// Explicit fallback servers (plain UDP) between primary and WAN-DNS fallback
if (key == "dns_server") {
for (let value in list_option(settings, "dns_fallback_server")) {
value = trim(as_string(value));
if (value != "")
push(result, value);
}
}
let fallback_key = (key == "dns_server") ? "fallback_wan_main" : "fallback_wan_bootstrap";
if (bool_option(settings, fallback_key, false)) {
for (let wan_ip in get_wan_dns_servers()) {
@@ -74,10 +82,22 @@ function configured_server_count(settings, key) {
return count;
}
function explicit_fallback_count(settings) {
return length(list_option(settings, "dns_fallback_server"));
}
function is_explicit_fallback_index(settings, index) {
let primary = configured_server_count(settings, "dns_server");
let fallback = explicit_fallback_count(settings);
return int(index) >= primary && int(index) < primary + fallback;
}
function is_wan_fallback_index(settings, index) {
if (!bool_option(settings, "fallback_wan_main", false))
return false;
return int(index) >= configured_server_count(settings, "dns_server");
let primary = configured_server_count(settings, "dns_server");
let fallback = explicit_fallback_count(settings);
return int(index) >= primary + fallback;
}
function state_template(settings) {
@@ -188,9 +208,10 @@ function bootstrap_server(tag_name, value) {
function server_config(settings, override_state) {
let active = active_values(settings, override_state);
let is_wan = is_wan_fallback_index(settings, active.state.main_index);
// WAN fallback servers must always use direct UDP — never detour through proxy
let dns_type = is_wan ? "udp" : active.state.dns_type;
let detour = is_wan ? "" : active.state.dns_detour;
let is_fallback = is_explicit_fallback_index(settings, active.state.main_index);
// WAN and explicit fallback servers must always use direct UDP — never detour through proxy
let dns_type = (is_wan || is_fallback) ? "udp" : active.state.dns_type;
let detour = (is_wan || is_fallback) ? "" : active.state.dns_detour;
let result = server_from_options(
runtime_constants.DNS_SERVER_TAG,
dns_type,
@@ -323,10 +344,14 @@ return {
arrays_equal,
bootstrap_config,
config,
configured_server_count,
default_domain_resolver,
detour_tag,
explicit_fallback_count,
failover_enabled,
health_port,
is_explicit_fallback_index,
is_wan_fallback_index,
normalize_state,
runtime_state,
server_config,
+30 -9
View File
@@ -229,18 +229,26 @@ function apply_selections(state, selections) {
let tcfg = common.object_or_empty(uci_core.get_all(CONFIG_NAME, "telegram"));
if (tcfg.enabled == "1" && tcfg.bot_token && tcfg.admin_ids) {
let is_isp = false;
let is_explicit_fallback = false;
let cfg = settings();
let conf_key = item.kind == "main" ? "dns_server" : "bootstrap_dns_server";
let configured_len = length(common.list_option(cfg, conf_key));
let configured_len = length(common.list_option(cfg, "dns_server"));
if (configured_len == 0) configured_len = 1;
let fallback_len = runtime_dns.explicit_fallback_count(cfg);
if (selected.index >= configured_len) {
is_isp = true;
if (item.kind == "main") {
if (selected.index >= configured_len && selected.index < configured_len + fallback_len) {
is_explicit_fallback = true;
} else if (selected.index >= configured_len + fallback_len) {
is_isp = true;
}
}
if (is_isp) {
let notice = "⚠️ Все основные " + item.kind + " DNS недоступны. Включен аварийный DNS провайдера (" + values[selected.index] + ")! Запросы теперь видит ваш провайдер.";
system(common.background_command(command_from_args([ "/usr/bin/tachyon", "telegram", "send", notice ])));
} else if (is_explicit_fallback) {
let notice = "⚠️ Основные DNS недоступны. Переключение на резервный DNS: " + values[selected.index] + ". Трафик зашифрован, но адрес указан вручную.";
system(common.background_command(command_from_args([ "/usr/bin/tachyon", "telegram", "send", notice ])));
} else if (selected.reason == "recovery" && selected.index < configured_len) {
let notice = "✅ " + item.kind + " DNS восстановлен. Возврат на безопасный сервер: " + values[selected.index];
system(common.background_command(command_from_args([ "/usr/bin/tachyon", "telegram", "send", notice ])));
@@ -258,9 +266,18 @@ function worker() {
if (!write_state(STATE_FILE, state))
return 1;
let active_interval = duration_seconds(common.option(cfg, "dns_check_interval", "10s"), 10);
let recovery_interval = duration_seconds(common.option(cfg, "dns_recovery_check_interval", "60s"), 60);
let timeout_seconds = probe_timeout(common.option(cfg, "dns_check_timeout", "2s"));
let mode = common.option(cfg, "dns_upstream_mode", "sequential");
let is_parallel = (mode == "parallel");
let active_interval = is_parallel
? 3
: duration_seconds(common.option(cfg, "dns_check_interval", "10s"), 10);
let recovery_interval = is_parallel
? 30
: duration_seconds(common.option(cfg, "dns_recovery_check_interval", "60s"), 60);
let timeout_seconds = is_parallel
? 2
: probe_timeout(common.option(cfg, "dns_check_timeout", "2s"));
let next_active = now_seconds();
let next_recovery = now_seconds() + recovery_interval;
let all_down = { main: false, bootstrap: false };
@@ -272,8 +289,12 @@ function worker() {
if (!runtime_dns.state_matches(runtime_dns.state_template(current_cfg), state))
return 0;
let failure_threshold = int(common.option(current_cfg, "dns_failure_threshold", "3"));
let recovery_threshold = int(common.option(current_cfg, "dns_recovery_threshold", "3"));
let failure_threshold = is_parallel
? 1
: int(common.option(current_cfg, "dns_failure_threshold", "3"));
let recovery_threshold = is_parallel
? 2
: int(common.option(current_cfg, "dns_recovery_threshold", "3"));
if (now >= next_active) {
let bootstrap = choose_index("bootstrap", state, int(state.bootstrap_index), timeout_seconds, false, strikes, failure_threshold, recovery_threshold);
+235
View File
@@ -0,0 +1,235 @@
#!/usr/bin/env bash
set -eo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
GENERATOR="$TACHYON_LIB/singbox/generator.uc"
FAILOVER="$TACHYON_LIB/singbox/dns_failover.uc"
WORK_DIR="$(mktemp -d)"
cleanup() {
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
generate() {
local fixture="$1"
local output="$2"
local state="${3:-$WORK_DIR/missing-state.json}"
TACHYON_LIB="$TACHYON_LIB" \
TACHYON_DNS_FAILOVER_STATE_FILE="$state" \
ucode -L "$TACHYON_LIB" "$GENERATOR" generate-config-fixture "$fixture" "$output" 192.168.1.1 0
}
# ─── Fixture 1: primary DoH + explicit fallback UDP ───────────────────────────
cat >"$WORK_DIR/with-fallback.json" <<'JSON'
{
"settings": {
".name": "settings",
".type": "settings",
"dns_type": "doh",
"dns_server": "dns.google/dns-query",
"dns_fallback_server": [ "1.1.1.1", "8.8.8.8" ],
"bootstrap_dns_server": "77.88.8.8"
},
"section": [
{
".name": "direct",
".type": "section",
"enabled": "1",
"action": "bypass",
"domain_suffix": [ "example.org" ]
}
]
}
JSON
# ─── Fixture 2: two primary + two fallback (all four as candidates) ────────────
cat >"$WORK_DIR/multi-with-fallback.json" <<'JSON'
{
"settings": {
".name": "settings",
".type": "settings",
"dns_type": "doh",
"dns_server": [ "dns.google/dns-query", "cloudflare-dns.com/dns-query" ],
"dns_fallback_server": [ "1.1.1.1", "8.8.8.8" ],
"bootstrap_dns_server": "77.88.8.8"
},
"section": [
{
".name": "direct",
".type": "section",
"enabled": "1",
"action": "bypass",
"domain_suffix": [ "example.org" ]
}
]
}
JSON
# ─── State: fallback active (index=1 points to first fallback) ────────────────
cat >"$WORK_DIR/fallback-state.json" <<'JSON'
{
"version": 1,
"dns_type": "doh",
"dns_detour": "",
"main_servers": [ "dns.google/dns-query", "1.1.1.1", "8.8.8.8" ],
"bootstrap_servers": [ "77.88.8.8" ],
"main_index": 1,
"bootstrap_index": 0
}
JSON
# ─── State: second fallback active (index=2) ─────────────────────────────────
cat >"$WORK_DIR/fallback-state2.json" <<'JSON'
{
"version": 1,
"dns_type": "doh",
"dns_detour": "",
"main_servers": [ "dns.google/dns-query", "1.1.1.1", "8.8.8.8" ],
"bootstrap_servers": [ "77.88.8.8" ],
"main_index": 2,
"bootstrap_index": 0
}
JSON
generate "$WORK_DIR/with-fallback.json" "$WORK_DIR/with-fallback-config.json"
generate "$WORK_DIR/with-fallback.json" "$WORK_DIR/fallback-active-config.json" "$WORK_DIR/fallback-state.json"
generate "$WORK_DIR/with-fallback.json" "$WORK_DIR/fallback-active2-config.json" "$WORK_DIR/fallback-state2.json"
generate "$WORK_DIR/multi-with-fallback.json" "$WORK_DIR/multi-with-fallback-config.json"
ucode -e '
let fs = require("fs");
function cfg(path) { return json(fs.readfile(path)); }
function assert(value, message) {
if (!value) { warn("FAIL: ", message, "\n"); exit(1); }
}
function find_tag(values, tag) {
for (let value in values || []) if (value.tag == tag) return value;
return null;
}
function count_prefix(values, prefix) {
let count = 0;
for (let value in values || []) if (index(value.tag || "", prefix) == 0) count++;
return count;
}
// ─── Test 1: primary DoH, no failover state — uses primary server ─────────────
let primary = cfg(ARGV[0]);
let main_srv = find_tag(primary.dns.servers, "dns-server");
assert(main_srv != null, "dns-server tag must exist");
assert(main_srv.type == "https", "primary DNS must be DoH type");
assert(main_srv.server == "dns.google", "primary DNS server must be dns.google");
// ─── Test 2: fallback state active (index=1 = 1.1.1.1) ───────────────────────
// Explicit fallback servers MUST always be plain UDP regardless of dns_type
let fallback_active = cfg(ARGV[1]);
let fb_main = find_tag(fallback_active.dns.servers, "dns-server");
assert(fb_main != null, "dns-server must exist in fallback-active config");
assert(fb_main.type == "udp", "explicit fallback DNS must use UDP even when dns_type=doh");
assert(fb_main.server == "1.1.1.1", "fallback server index 1 must be 1.1.1.1");
assert(fb_main.detour == null, "explicit fallback must not use proxy detour");
// ─── Test 3: second fallback server (index=2 = 8.8.8.8) ──────────────────────
let fallback_active2 = cfg(ARGV[2]);
let fb_main2 = find_tag(fallback_active2.dns.servers, "dns-server");
assert(fb_main2 != null, "dns-server must exist in fallback-active2 config");
assert(fb_main2.type == "udp", "second explicit fallback must also be UDP");
assert(fb_main2.server == "8.8.8.8", "fallback server index 2 must be 8.8.8.8");
// ─── Test 4: multi-primary with fallback generates health candidates ──────────
let multi = cfg(ARGV[3]);
// 2 main health candidates + 2 fallback candidates + 1 active-main inbound = 5 health rules
let health_rules = 0;
for (let rule in multi.dns.rules || []) {
if (index(rule.inbound || "", "dns-health-") == 0) {
health_rules++;
assert(rule.disable_cache === true, "health checks must bypass DNS cache");
}
}
// We have 2 primary + 2 fallback = 4 main candidates (all generate health probes)
// Plus 1 bootstrap (single) = no bootstrap health
// Plus 1 active health inbound = total 5 DNS health rules
assert(health_rules == 5, "2 main + 2 fallback candidates + 1 active = 5 health rules, got " + health_rules);
assert(count_prefix(multi.dns.servers, "dns-health-main-") == 4, "4 main health servers (2 primary + 2 fallback)");
' "$WORK_DIR/with-fallback-config.json" \
"$WORK_DIR/fallback-active-config.json" \
"$WORK_DIR/fallback-active2-config.json" \
"$WORK_DIR/multi-with-fallback-config.json"
# ─── Test 5: is_explicit_fallback_index logic via dns.uc ─────────────────────
ucode -L "$TACHYON_LIB" -e '
let runtime_dns = require("singbox.dns");
let common = require("core.common");
function assert(value, message) {
if (!value) { warn("FAIL: ", message, "\n"); exit(1); }
}
let settings_one_primary_two_fallback = {
dns_server: [ "dns.google/dns-query" ],
dns_fallback_server: [ "1.1.1.1", "8.8.8.8" ]
};
// configured_server_count: 1 (primary)
// explicit_fallback_count: 2
// index 0 = primary, index 1 = fallback, index 2 = fallback
assert(runtime_dns.is_explicit_fallback_index(settings_one_primary_two_fallback, 0) == false, "index 0 is primary, not fallback");
assert(runtime_dns.is_explicit_fallback_index(settings_one_primary_two_fallback, 1) == true, "index 1 is first explicit fallback");
assert(runtime_dns.is_explicit_fallback_index(settings_one_primary_two_fallback, 2) == true, "index 2 is second explicit fallback");
assert(runtime_dns.is_wan_fallback_index(settings_one_primary_two_fallback, 1) == false, "index 1 is NOT WAN fallback");
assert(runtime_dns.is_wan_fallback_index(settings_one_primary_two_fallback, 2) == false, "index 2 is NOT WAN fallback");
let settings_with_wan = {
dns_server: [ "dns.google/dns-query" ],
dns_fallback_server: [ "1.1.1.1" ],
fallback_wan_main: "1"
};
// index 0 = primary, index 1 = explicit fallback, index 2+ = WAN
assert(runtime_dns.is_explicit_fallback_index(settings_with_wan, 1) == true, "index 1 is explicit fallback even with WAN enabled");
assert(runtime_dns.is_wan_fallback_index(settings_with_wan, 1) == false, "index 1 is NOT WAN (is explicit fallback)");
// WAN starts at index 2 (1 primary + 1 fallback)
assert(runtime_dns.is_wan_fallback_index(settings_with_wan, 2) == true, "index 2 is WAN fallback");
let settings_no_fallback = {
dns_server: [ "dns.google/dns-query", "cloudflare-dns.com/dns-query" ],
fallback_wan_main: "1"
};
// index 0,1 = primary, index 2+ = WAN (no explicit fallback)
assert(runtime_dns.is_explicit_fallback_index(settings_no_fallback, 1) == false, "index 1 is primary, no explicit fallback defined");
assert(runtime_dns.is_wan_fallback_index(settings_no_fallback, 2) == true, "index 2 is WAN fallback when no explicit fallback");
'
# ─── Test 6: server_list includes dns_fallback_server entries ─────────────────
ucode -L "$TACHYON_LIB" -e '
let runtime_dns = require("singbox.dns");
function assert(value, message) {
if (!value) { warn("FAIL: ", message, "\n"); exit(1); }
}
let settings = {
dns_server: [ "dns.google/dns-query", "cloudflare-dns.com/dns-query" ],
dns_fallback_server: [ "1.1.1.1", "8.8.8.8" ]
};
let servers = runtime_dns.server_list(settings, "dns_server", "77.88.8.8");
assert(length(servers) == 4, "server_list must include 2 primary + 2 fallback = 4 total");
assert(servers[0] == "dns.google/dns-query", "first server is primary[0]");
assert(servers[1] == "cloudflare-dns.com/dns-query", "second server is primary[1]");
assert(servers[2] == "1.1.1.1", "third server is fallback[0]");
assert(servers[3] == "8.8.8.8", "fourth server is fallback[1]");
// Bootstrap list must NOT include dns_fallback_server entries
let bootstrap = runtime_dns.server_list(settings, "bootstrap_dns_server", "77.88.8.8");
assert(length(bootstrap) == 1, "bootstrap list must not include dns_fallback_server entries");
assert(bootstrap[0] == "77.88.8.8", "bootstrap falls back to default");
'
printf 'DNS fallback server checks passed\n'
+2
View File
@@ -539,6 +539,8 @@ cat >"$WORK_DIR/sing-box-signature.expected" <<'EOF_SING_BOX_SIG'
tcp
[settings.dns_strategy]
prefer_ipv6
[settings.dns_upstream_mode]
sequential
[settings.dns_server]
9.9.9.9
[settings.bootstrap_dns_server]