fix: zapret2 startup, dnsmasq fakeip rebind protection, dns detour routes (1.3.19)

This commit is contained in:
Dushnilin
2026-09-04 13:56:48 +03:00
parent 196782b18d
commit e901f9d41a
16 changed files with 363 additions and 29 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "fe-app-tachyon",
"version": "1.3.18",
"version": "1.3.19",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "fe-app-tachyon",
"version": "1.3.18",
"version": "1.3.19",
"license": "MIT",
"dependencies": {
"@esbuild/win32-x64": "^0.28.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "fe-app-tachyon",
"version": "1.3.18",
"version": "1.3.19",
"license": "MIT",
"type": "module",
"scripts": {
+2 -2
View File
@@ -16,8 +16,8 @@ export const DOMAIN_LIST_OPTIONS = {
russia_inside: 'Russia inside',
russia_outside: 'Russia outside',
ukraine_inside: 'Ukraine',
geoblock: 'Geo Block',
block: 'Block',
geoblock: 'Geo Block (снаружи РФ)',
block: 'Block (РКН / внутри РФ)',
porn: 'Porn',
news: 'News',
anime: 'Anime',
@@ -350,7 +350,7 @@ export const TachyonShellMethods = {
doctor: async () =>
callBaseMethod<string>(
Tachyon.AvailableMethods.DOCTOR,
[],
['--fix'],
'/usr/bin/tachyon',
{ timeout: 30000 },
),
@@ -34,7 +34,9 @@ export function getDnsCheckPresentation(data: Tachyon.DnsCheckResult) {
: 'error';
const dhcpItemKey = dhcpManagedManually
? _('DHCP is managed manually')
: _('DHCP has DNS server');
: data.dhcp_config_status
? _('DHCP forwards DNS to Tachyon')
: _('DHCP does not forward DNS to Tachyon');
return {
state,
@@ -36,7 +36,21 @@ describe('getDnsCheckPresentation', () => {
state: 'warning',
description: 'Issues detected',
dhcpItemState: 'error',
dhcpItemKey: 'DHCP has DNS server',
dhcpItemKey: 'DHCP does not forward DNS to Tachyon',
});
});
it('shows success when DHCP forwards DNS to Tachyon', () => {
expect(
getDnsCheckPresentation({
...baseDnsResult,
dhcp_config_status: 1,
}),
).toMatchObject({
state: 'success',
description: 'Checks passed',
dhcpItemState: 'success',
dhcpItemKey: 'DHCP forwards DNS to Tachyon',
});
});
});
@@ -1095,8 +1095,8 @@ var DOMAIN_LIST_OPTIONS = {
russia_inside: "Russia inside",
russia_outside: "Russia outside",
ukraine_inside: "Ukraine",
geoblock: "Geo Block",
block: "Block",
geoblock: "Geo Block (\u0441\u043D\u0430\u0440\u0443\u0436\u0438 \u0420\u0424)",
block: "Block (\u0420\u041A\u041D / \u0432\u043D\u0443\u0442\u0440\u0438 \u0420\u0424)",
porn: "Porn",
news: "News",
anime: "Anime",
@@ -3497,7 +3497,7 @@ var TachyonShellMethods = {
]),
doctor: async () => callBaseMethod(
Tachyon.AvailableMethods.DOCTOR,
[],
["--fix"],
"/usr/bin/tachyon",
{ timeout: 3e4 }
),
@@ -9662,7 +9662,7 @@ function getDnsCheckPresentation(data) {
const state = dhcpManagedManually && meta.state === "success" ? "warning" : meta.state;
const description = dhcpManagedManually && meta.state === "success" ? _("Checks passed with manual DHCP") : meta.description;
const dhcpItemState = dhcpManagedManually ? "warning" : data.dhcp_config_status ? "success" : "error";
const dhcpItemKey = dhcpManagedManually ? _("DHCP is managed manually") : _("DHCP has DNS server");
const dhcpItemKey = dhcpManagedManually ? _("DHCP is managed manually") : data.dhcp_config_status ? _("DHCP forwards DNS to Tachyon") : _("DHCP does not forward DNS to Tachyon");
return {
state,
description,
@@ -7126,8 +7126,8 @@ var DOMAIN_LIST_HINTS = {
russia_inside: "youtube.com, instagram.com, twitter.com, ...",
russia_outside: "gosuslugi.ru, ozon.ru, rzd.ru, ...",
ukraine_inside: "ukr.net, pravda.com.ua, suspilne.media, ...",
geoblock: "Все домены, заблокированные в РФ",
block: "Заблокировать совсем (без прокси)",
geoblock: "Домены, заблокированные снаружи России (геоблокировка зарубежными сервисами)",
block: "Домены, заблокированные внутри России (полный реестр блокировок РКН)",
porn: "Pornhub, xvideos, xhamster, ...",
news: "meduza.io, bbc.com, pravda.com.ua, ...",
anime: "bato.to, mangadex.org, myanimelist.net, ...",
@@ -7157,8 +7157,14 @@ function loadRulesetValues(option) {
delete option.vallist;
Object.entries(main.DOMAIN_LIST_OPTIONS).forEach(([key, label]) => {
let displayLabel = _(label);
if (key === "block") {
displayLabel = "Block (РКН / внутри РФ)";
} else if (key === "geoblock") {
displayLabel = "Geo Block (снаружи РФ)";
}
const hint = DOMAIN_LIST_HINTS[key];
option.value(key, hint ? `${_(label)} — ${hint}` : _(label));
option.value(key, hint ? `${displayLabel} — ${hint}` : displayLabel);
});
}
@@ -9869,7 +9875,8 @@ function createSectionContent(section) {
form.DynamicList,
"community_lists",
_("Built-in rule sets"),
_("Select a predefined domain list"),
_("Select a predefined domain list") +
' (source: <a href="https://github.com/itdoginfo/allow-domains" target="_blank" rel="noopener noreferrer">itdoginfo/allow-domains</a>)',
);
builtInRulesetOption.modalonly = true;
builtInRulesetOption.placeholder = _("Service list");
@@ -10408,6 +10415,44 @@ async function performTrace(query) {
queryForMatching = query.split("/")[0];
}
// Check DNS Detour from settings if enabled
const dnsDetourEnabled = uci.get(UCI_PACKAGE, "settings", "dns_detour_enabled");
if (dnsDetourEnabled === "1") {
const detourSection = uci.get(UCI_PACKAGE, "settings", "dns_detour_section");
if (detourSection) {
const dnsServers = normalizeOptionValues(uci.get(UCI_PACKAGE, "settings", "dns_server"));
const fallbackServers = normalizeOptionValues(uci.get(UCI_PACKAGE, "settings", "dns_fallback_server"));
const allDns = [...dnsServers, ...fallbackServers];
const detourIps = [];
const detourDomains = [];
for (let s of allDns) {
if (!s) continue;
s = s.split("#")[0].split("/")[0].trim();
if (s.match(/^[0-9.]+$|^[0-9a-fA-F:]+$/)) {
detourIps.push(s);
} else if (s) {
detourDomains.push(s);
}
}
const isDnsMatch = (type === "ip" && detourIps.includes(queryForMatching)) ||
(type === "domain" && detourDomains.includes(queryForMatching));
if (isDnsMatch) {
const label = uci.get(UCI_PACKAGE, detourSection, "label") || detourSection;
const action = uci.get(UCI_PACKAGE, detourSection, "action") || "connection";
return {
matched: true,
sectionName: detourSection,
label: label,
action: action,
ruleType: "DNS Detour (Settings)",
pattern: queryForMatching,
priority: 0,
totalSections: totalSections,
};
}
}
}
for (let i = 0; i < sections.length; i++) {
const sec = sections[i];
const secName = sec[".name"];
+57 -1
View File
@@ -1239,7 +1239,12 @@ function get_server_capabilities() {
}
function neutralize_zapret_defaults() {
log_message("Standalone zapret is not neutralized automatically; Tachyon uses /opt/zapret/nfq/nfqws as an external provider and manages only its own NFQUEUE range.", "info");
log_message("Neutralizing standalone zapret/zapret2/byedpi services and tables", "info");
command_status("/etc/init.d/zapret stop >/dev/null 2>&1; /etc/init.d/zapret disable >/dev/null 2>&1 || true");
command_status("/etc/init.d/zapret2 stop >/dev/null 2>&1; /etc/init.d/zapret2 disable >/dev/null 2>&1 || true");
command_status("/etc/init.d/byedpi stop >/dev/null 2>&1; /etc/init.d/byedpi disable >/dev/null 2>&1 || true");
command_status("nft delete table inet zapret >/dev/null 2>&1 || true");
command_status("nft delete table inet zapret2 >/dev/null 2>&1 || true");
return 0;
}
@@ -2839,6 +2844,57 @@ function run_doctor_checks_impl(repair) {
}
}
// 6d. DPI bypass providers health (Zapret, Zapret2, ByeDPI)
let providers_to_check = [
{ name: "Zapret", kind: "zapret", runtime: ZAPRET_RUNTIME_UC },
{ name: "Zapret2", kind: "zapret2", runtime: ZAPRET2_RUNTIME_UC },
{ name: "ByeDPI", kind: "byedpi", runtime: BYEDPI_RUNTIME_UC }
];
for (let p in providers_to_check) {
let raw_st = trim(command_capture("ucode -L " + LIB_DIR + " " + p.runtime + " status 2>/dev/null").output);
if (raw_st == "") continue;
let st = null;
try { st = json(raw_st); } catch (e) {}
if (!st || st.configured != true) continue;
// Check for standalone service conflict
if (st.standalone_conflict == true || st.standalone_service_running == true) {
issues++;
if (!DOCTOR_REPAIR_MODE) {
doc_plan("/etc/init.d/" + p.kind + " stop && /etc/init.d/" + p.kind + " disable && nft delete table inet " + p.kind);
doc_check("⚠️", "Standalone " + p.name, "active conflict", "→ WILL FIX: отключение автономной службы и удаление конфликтующей таблицы");
} else {
command_status("/etc/init.d/" + p.kind + " stop >/dev/null 2>&1; /etc/init.d/" + p.kind + " disable >/dev/null 2>&1; nft delete table inet " + p.kind + " >/dev/null 2>&1 || true");
doc_check("❌", "Standalone " + p.name, "active conflict", "→ FIXED: автономная служба отключена, таблица очищена");
fixed++;
}
}
// Check if Tachyon-managed runtime is ready
if (st.ready == true) {
doc_check("✅", p.name + " runtime", sprintf("ready (%d/%d workers)", st.running_process_count || 0, st.expected_process_count || 0), "");
} else {
issues++;
if (!DOCTOR_REPAIR_MODE) {
doc_plan("ucode -L " + LIB_DIR + " " + p.runtime + " start-runtime");
doc_check("⚠️", p.name + " runtime", sprintf("not ready (%d/%d workers)", st.running_process_count || 0, st.expected_process_count || 0), "→ WILL FIX: запуск воркеров Tachyon " + p.name);
} else {
command_status("ucode -L " + LIB_DIR + " " + p.runtime + " start-runtime >/dev/null 2>&1");
command_status("sleep 1");
let raw_st2 = trim(command_capture("ucode -L " + LIB_DIR + " " + p.runtime + " status 2>/dev/null").output);
let st2 = null;
try { st2 = json(raw_st2); } catch (e) {}
if (st2 && st2.ready == true) {
doc_check("❌", p.name + " runtime", sprintf("not ready (%d/%d workers)", st.running_process_count || 0, st.expected_process_count || 0), "→ FIXED: воркеры " + p.name + " запущены");
fixed++;
} else {
doc_check("❌", p.name + " runtime", "failed to start", "→ не удалось запустить воркеры " + p.name);
}
}
}
}
// 7. Free RAM Check
let free_mb = -1;
let mem_info = fs.readfile("/proc/meminfo") || "";
+29 -3
View File
@@ -58,9 +58,15 @@ function truthy(value) {
return value == "1" || value == "true" || value == "yes" || value == "on";
}
function starts_with(str, prefix) {
str = as_string(str);
prefix = as_string(prefix);
return substr(str, 0, length(prefix)) == prefix;
}
function list_has(values, needle) {
for (let value in words(values))
if (value == needle)
if (value == needle || (needle != "" && starts_with(value, needle + "#")))
return true;
return false;
}
@@ -106,6 +112,8 @@ function dnsmasq_has_tachyon_managed_state() {
return uci_get("dhcp.@dnsmasq[0].tachyon_server") != "" ||
uci_get("dhcp.@dnsmasq[0].tachyon_noresolv") != "" ||
uci_get("dhcp.@dnsmasq[0].tachyon_cachesize") != "" ||
uci_get("dhcp.@dnsmasq[0].tachyon_rebind_protection") != "" ||
uci_get("dhcp.@dnsmasq[0].tachyon_localuse") != "" ||
uci_get("dhcp.@dnsmasq[0].tachyon_notinterface") != "" ||
dnsmasq_legacy_instance_exists();
}
@@ -118,6 +126,8 @@ function dnsmasq_default_config_is_complete() {
return dnsmasq_default_has_tachyon_dns() &&
uci_get("dhcp.@dnsmasq[0].noresolv") == "1" &&
uci_get("dhcp.@dnsmasq[0].cachesize") == "0" &&
uci_get("dhcp.@dnsmasq[0].rebind_protection") == "0" &&
uci_get("dhcp.@dnsmasq[0].localuse") == "1" &&
!dnsmasq_legacy_instance_exists();
}
@@ -146,7 +156,7 @@ function backup_dnsmasq_server_list() {
return;
for (let server in words(dnsmasq_default_servers())) {
if (server != SB_DNS_INBOUND_ADDRESS)
if (server != SB_DNS_INBOUND_ADDRESS && !starts_with(server, SB_DNS_INBOUND_ADDRESS + "#"))
uci_add_list("dhcp.@dnsmasq[0].tachyon_server", server);
}
}
@@ -195,6 +205,8 @@ function dnsmasq_configure_default_instance() {
if (!default_has_tachyon_dns) {
backup_dnsmasq_config_option("noresolv", "tachyon_noresolv");
backup_dnsmasq_config_option("cachesize", "tachyon_cachesize");
backup_dnsmasq_config_option("rebind_protection", "tachyon_rebind_protection");
backup_dnsmasq_config_option("localuse", "tachyon_localuse");
}
backup_dnsmasq_config_option("addn_hosts", "tachyon_addn_hosts");
@@ -204,6 +216,8 @@ function dnsmasq_configure_default_instance() {
uci_add_list("dhcp.@dnsmasq[0].server", SB_DNS_INBOUND_ADDRESS);
uci_set("dhcp.@dnsmasq[0].noresolv", "1");
uci_set("dhcp.@dnsmasq[0].cachesize", "0");
uci_set("dhcp.@dnsmasq[0].rebind_protection", "0");
uci_set("dhcp.@dnsmasq[0].localuse", "1");
}
function dnsmasq_restore_default_instance() {
@@ -219,7 +233,7 @@ function dnsmasq_restore_default_instance() {
}
else {
for (let value in words(server_list)) {
if (value != SB_DNS_INBOUND_ADDRESS)
if (value != SB_DNS_INBOUND_ADDRESS && !starts_with(value, SB_DNS_INBOUND_ADDRESS + "#"))
uci_add_list("dhcp.@dnsmasq[0].server", value);
}
}
@@ -237,6 +251,18 @@ function dnsmasq_restore_default_instance() {
else if (managed_global_dns)
uci_set("dhcp.@dnsmasq[0].cachesize", "150");
let rebind_protection = uci_get("dhcp.@dnsmasq[0].tachyon_rebind_protection");
if (rebind_protection != "")
restore_dnsmasq_config_option("rebind_protection", "tachyon_rebind_protection", "");
else if (managed_global_dns)
uci_delete("dhcp.@dnsmasq[0].rebind_protection");
let localuse = uci_get("dhcp.@dnsmasq[0].tachyon_localuse");
if (localuse != "")
restore_dnsmasq_config_option("localuse", "tachyon_localuse", "");
else if (managed_global_dns)
uci_delete("dhcp.@dnsmasq[0].localuse");
restore_dnsmasq_config_option("addn_hosts", "tachyon_addn_hosts", "");
}
@@ -304,8 +304,10 @@ function start_runtime() {
if (length(sections) == 0 || !provider_available())
return;
if (standalone_service_enabled())
log_message("Standalone byedpi service is enabled. Tachyon manages ciadpi itself for action 'byedpi'; disable standalone byedpi autostart to avoid boot-time port conflicts.", "warn");
if (standalone_service_enabled()) {
log_message("Disabling standalone byedpi autostart to avoid port conflicts", "warn");
command_status_from_args([ BYEDPI_SERVICE_INIT, "disable" ]);
}
if (standalone_service_running()) {
log_message("Stopping standalone byedpi service before starting Tachyon-managed ciadpi runtime", "info");
@@ -411,6 +411,23 @@ function start_rule(cfg, section, index_value) {
return true;
}
function neutralize_standalone_service(cfg) {
if (fs.stat(cfg.service_init) != null) {
if (command_success_from_args([ cfg.service_init, "status" ])) {
log_message("Stopping conflicting standalone " + cfg.status_label + " service before starting Tachyon runtime", "info");
command_status_from_args([ cfg.service_init, "stop" ]);
command_status_from_args([ "sleep", "1" ]);
}
if (command_success_from_args([ cfg.service_init, "enabled" ])) {
log_message("Disabling conflicting standalone " + cfg.status_label + " autostart", "warn");
command_status_from_args([ cfg.service_init, "disable" ]);
}
}
if (cfg.kind != "") {
command_status_from_args([ "nft", "delete", "table", "inet", cfg.kind ]);
}
}
function start_runtime(cfg) {
stop_runtime(cfg);
@@ -418,6 +435,7 @@ function start_runtime(cfg) {
if (length(sections) == 0 || !provider_available(cfg))
return;
neutralize_standalone_service(cfg);
cleanup_legacy_runtime(cfg);
if (!ensure_runtime_dirs(cfg)) {
log_message("Failed to prepare the Tachyon " + cfg.status_label + " state directory in " + cfg.state_dir + ". Aborted.", "fatal");
@@ -621,6 +639,9 @@ function create_nft_rules(cfg) {
if (length(sections) == 0 || !provider_available(cfg))
return;
if (cfg.kind != "")
command_status_from_args([ "nft", "delete", "table", "inet", cfg.kind ]);
command_success_from_args([ "nft", "flush", "chain", "inet", NFT_TABLE_NAME, "mangle_output" ]);
command_success_from_args([ "nft", "add", "rule", "inet", NFT_TABLE_NAME, "mangle_output", "meta", "mark", "&", cfg.desync_mark, "==", cfg.desync_mark, "return" ]);
@@ -648,6 +669,8 @@ function run(provider, argv) {
start_runtime(cfg);
else if (mode == "stop-runtime")
stop_runtime(cfg);
else if (mode == "neutralize-standalone")
exit(neutralize_standalone_service(cfg) ? 0 : 0);
else if (mode == "create-nft-rules")
create_nft_rules(cfg);
else if (mode == "status")
@@ -1,6 +1,7 @@
#!/usr/bin/env ucode
let constants = require("core.constants");
let fs = require("fs");
let validator_module = null;
const LIB_DIR = getenv("TACHYON_LIB") || "/usr/lib/tachyon";
@@ -17,6 +18,40 @@ function config(ctx) {
let desync_mark = getenv("ZAPRET2_DESYNC_MARK") || runtime_constants.ZAPRET2_DESYNC_MARK;
let provider_lua_dir = getenv("ZAPRET2_PROVIDER_LUA_DIR") || runtime_constants.ZAPRET2_PROVIDER_LUA_DIR;
let base_args = [
"--fwmark=" + desync_mark
];
let candidate_dirs = [
provider_lua_dir,
"/opt/zapret2/lua",
"/usr/share/zapret2/lua",
"/etc/zapret2/lua",
"/usr/lib/zapret2/lua"
];
let lua_scripts = [
"zapret-lib.lua",
"zapret-antidpi.lua",
"zapret-auto.lua"
];
for (let script in lua_scripts) {
let found = null;
for (let dir in candidate_dirs) {
if (!dir)
continue;
let p = dir + "/" + script;
if (fs.stat(p) != null) {
found = p;
break;
}
if (fs.stat(p + ".gz") != null) {
found = p + ".gz";
break;
}
}
if (found != null)
push(base_args, "--lua-init=@" + found);
}
return {
kind: "zapret2",
action: "zapret2",
@@ -53,12 +88,7 @@ function config(ctx) {
hostlist_dir: "",
status_label: "zapret2",
check_prefix: "zapret2",
base_args: [
"--fwmark=" + desync_mark,
"--lua-init=@" + provider_lua_dir + "/zapret-lib.lua",
"--lua-init=@" + provider_lua_dir + "/zapret-antidpi.lua",
"--lua-init=@" + provider_lua_dir + "/zapret-auto.lua"
]
base_args
};
}
+66
View File
@@ -27,6 +27,8 @@ let telegram_msg_window = time();
let telegram_msg_history = {};
let tailscale_fail_streak = 0;
let tailscale_last_alert = 0;
let provider_fail_streaks = {};
let dnsmasq_redirection_fail_streak = 0;
let command_from_args = common.command_from_args;
let command_status = common.command_status;
@@ -2023,12 +2025,76 @@ function check_section_failover() {
command_status("/usr/bin/tachyon failover_check >/dev/null 2>&1");
}
// ── DPI provider workers (Zapret, Zapret2, ByeDPI) self-healing ───────────────
function check_provider_workers() {
let providers = [
{ kind: "zapret", runtime: LIB_DIR + "/providers/zapret/runtime.uc" },
{ kind: "zapret2", runtime: LIB_DIR + "/providers/zapret2/runtime.uc" },
{ kind: "byedpi", runtime: LIB_DIR + "/providers/byedpi/runtime.uc" }
];
for (let p in providers) {
let raw = trim(command_output_from_args([ "/usr/bin/tachyon", "get_" + p.kind + "_status" ]) || "");
if (raw == "")
continue;
let st = null;
try { st = json(raw); } catch (e) {}
if (!st || st.configured != true)
continue;
// Auto-neutralize standalone conflict if running or enabled
if (st.standalone_conflict == true || st.standalone_service_running == true) {
log_message(sprintf("Watchdog: Conflicting standalone %s service detected; neutralizing it.", p.kind), "warn");
command_status_from_args([ "/etc/init.d/" + p.kind, "stop" ]);
command_status_from_args([ "/etc/init.d/" + p.kind, "disable" ]);
command_status_from_args([ "nft", "delete", "table", "inet", p.kind ]);
}
// Auto-heal dead worker processes
if (st.ready != true) {
let streak = int(provider_fail_streaks[p.kind] || 0) + 1;
provider_fail_streaks[p.kind] = streak;
log_message(sprintf("Watchdog: %s runtime is unhealthy (%s, streak %d); restarting.",
p.kind, as_string(st.status_message || "workers down"), streak), "warn");
command_status_from_args([ "ucode", "-L", LIB_DIR, p.runtime, "start-runtime" ]);
} else {
if (int(provider_fail_streaks[p.kind] || 0) > 0)
log_message(sprintf("Watchdog: %s runtime recovered.", p.kind), "info");
provider_fail_streaks[p.kind] = 0;
}
}
}
// ── Dnsmasq redirection watchdog ─────────────────────────────────────────────
function check_dnsmasq_redirection() {
let cfg = settings();
if (as_string(cfg.dont_touch_dhcp || "0") == "1")
return;
if (!is_process_name_running("sing-box"))
return;
let has_dns = command_status_from_args([ "ucode", "-L", LIB_DIR, LIB_DIR + "/dns/apply.uc", "default-config-complete" ]);
if (has_dns == 0) {
if (dnsmasq_redirection_fail_streak > 0)
log_message("Watchdog: dnsmasq redirection recovered.", "info");
dnsmasq_redirection_fail_streak = 0;
return;
}
dnsmasq_redirection_fail_streak++;
log_message(sprintf("Watchdog: dnsmasq is not redirecting to Tachyon DNS (streak %d); re-applying configuration.",
dnsmasq_redirection_fail_streak), "warn");
command_status_from_args([ "ucode", "-L", LIB_DIR, LIB_DIR + "/dns/apply.uc", "configure", "force" ]);
}
function perform_slow_checks() {
controller.probe_slow();
safe_call(ai_heal_dns_loop, "ai_heal_dns_loop");
safe_call(check_mixed_proxy_port, "check_mixed_proxy_port");
safe_call(check_telegram_worker, "check_telegram_worker");
safe_call(check_tailscale_worker, "check_tailscale_worker");
safe_call(check_provider_workers, "check_provider_workers");
safe_call(check_dnsmasq_redirection, "check_dnsmasq_redirection");
safe_call(check_section_failover, "check_section_failover");
}
if (uloop) {
@@ -3,6 +3,8 @@
let fs = require("fs");
let common = require("core.common");
let helpers = require("core.helpers");
let core_ip = require("core.ip");
let core_url = require("core.url");
let runtime_constants = require("singbox.constants");
let runtime_dns = require("singbox.dns");
let runtime_route = require("singbox.route");
@@ -1503,9 +1505,67 @@ function failover_default_name(candidates) {
const FAILOVER_GROUP_TAG = "tachyon-failover";
function add_service_route_rules(config, sections) {
let settings = object_or_empty(ctx.uci_cursor().get_all(CONFIG_NAME, "settings"));
if (bool_option(settings, "dns_detour_enabled", false)) {
let detour_section = option(settings, "dns_detour_section", "");
if (detour_section != "") {
let detour_target = outbound_tag(detour_section);
let dns_servers = list_option(settings, "dns_server");
if (length(dns_servers) == 0) {
let single_dns = option(settings, "dns_server", "");
if (single_dns != "")
dns_servers = [single_dns];
}
let fallback_servers = list_option(settings, "dns_fallback_server");
if (length(fallback_servers) == 0) {
let single_fb = option(settings, "dns_fallback_server", "");
if (single_fb != "")
fallback_servers = [single_fb];
}
let all_dns = [];
for (let s in dns_servers)
push(all_dns, s);
for (let s in fallback_servers)
push(all_dns, s);
let detour_ips = [];
let detour_domains = [];
for (let s in all_dns) {
s = trim(as_string(s));
if (s == "")
continue;
s = split(s, "#")[0];
let host = core_url.host(s);
if (host == "")
host = s;
host = split(host, "/")[0];
if (core_ip.valid_ipv4(host) || core_ip.valid_ipv6(host))
push(detour_ips, host);
else if (host != "")
push(detour_domains, host);
}
if (length(detour_ips) > 0) {
push(config.route.rules, {
action: "route",
inbound: tproxy_inbound_matcher(),
outbound: detour_target,
ip_cidr: single_or_array(detour_ips)
});
}
if (length(detour_domains) > 0) {
push(config.route.rules, {
action: "route",
inbound: tproxy_inbound_matcher(),
outbound: detour_target,
domain: single_or_array(detour_domains)
});
}
}
}
let candidates = failover_candidate(sections);
let first = length(candidates) > 0 ? candidates[0] : null;
let failover_active = bool_option(object_or_empty(ctx.uci_cursor().get_all(CONFIG_NAME, "settings")), "section_failover_enabled", false) &&
let failover_active = bool_option(settings, "section_failover_enabled", false) &&
length(candidates) > 1;
if (first != null) {
push(config.route.rules, {
+10
View File
@@ -186,6 +186,8 @@ cat >"$STATE" <<'EOF_STATE'
dhcp.@dnsmasq[0].server=1.1.1.1 8.8.8.8
dhcp.@dnsmasq[0].noresolv=0
dhcp.@dnsmasq[0].cachesize=150
dhcp.@dnsmasq[0].rebind_protection=1
dhcp.@dnsmasq[0].localuse=0
tachyon.settings.shutdown_correctly=1
EOF_STATE
@@ -198,6 +200,10 @@ assert_value 'dhcp.@dnsmasq[0].noresolv' '1'
assert_value 'dhcp.@dnsmasq[0].tachyon_noresolv' '0'
assert_value 'dhcp.@dnsmasq[0].cachesize' '0'
assert_value 'dhcp.@dnsmasq[0].tachyon_cachesize' '150'
assert_value 'dhcp.@dnsmasq[0].rebind_protection' '0'
assert_value 'dhcp.@dnsmasq[0].tachyon_rebind_protection' '1'
assert_value 'dhcp.@dnsmasq[0].localuse' '1'
assert_value 'dhcp.@dnsmasq[0].tachyon_localuse' '0'
assert_log_contains 'commit dhcp'
assert_dnsmasq_reloaded
@@ -207,9 +213,13 @@ ucode -L "$UCODE_LIB" "$APPLY" restore force
assert_value 'dhcp.@dnsmasq[0].server' '1.1.1.1 8.8.8.8'
assert_value 'dhcp.@dnsmasq[0].noresolv' '0'
assert_value 'dhcp.@dnsmasq[0].cachesize' '150'
assert_value 'dhcp.@dnsmasq[0].rebind_protection' '1'
assert_value 'dhcp.@dnsmasq[0].localuse' '0'
assert_absent 'dhcp.@dnsmasq[0].tachyon_server'
assert_absent 'dhcp.@dnsmasq[0].tachyon_noresolv'
assert_absent 'dhcp.@dnsmasq[0].tachyon_cachesize'
assert_absent 'dhcp.@dnsmasq[0].tachyon_rebind_protection'
assert_absent 'dhcp.@dnsmasq[0].tachyon_localuse'
assert_log_contains 'commit dhcp'
assert_dnsmasq_reloaded