fix: zapret2 startup, dnsmasq fakeip rebind protection, dns detour routes (1.3.19)
This commit is contained in:
Generated
+2
-2
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "fe-app-tachyon",
|
||||
"version": "1.3.18",
|
||||
"version": "1.3.19",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -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,
|
||||
|
||||
+15
-1
@@ -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"];
|
||||
|
||||
@@ -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") || "";
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user