feat(dns): upstream mode (sequential/parallel) & explicit fallback DNS servers, bump 1.3.13
This commit is contained in:
@@ -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) {
|
||||
|
||||
+2150
-1923
File diff suppressed because it is too large
Load Diff
+2063
-1923
File diff suppressed because it is too large
Load Diff
@@ -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 для удаленного управления"
|
||||
|
||||
Generated
+2
-2
@@ -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,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",
|
||||
|
||||
@@ -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 для удаленного управления"
|
||||
|
||||
+2063
-1923
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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'
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user