feat: per-section MAC/DNS exclusions, boot latency sweep, WDTT/OlcRTC providers, DNS hijack socket collision fix

Features:
- Per-Section MAC Exclusions: nft, generator_routes, section.js, i18n
- Per-Section Routed DNS: connections, generator_routes, nft, validator,
  types.ts, section.js, state.uc, reload.uc
- Boot Latency Sweep: ui.uc latency boot sweep with fixture dispatchers
- WDTT provider: full backend (common, validator, runtime), sing-box
  outbound, diagnostics, component install, updater, i18n, section.js UI
- OlcRTC provider: full backend (common, validator, runtime), sing-box
  outbound, diagnostics, component install, updater, i18n, section.js UI
- WireGuard-over-TURN tunnel (WDTT) and TCP-over-WebRTC tunnel (OlcRTC)
  as sidecar processes with local SOCKS5, routed through sing-box outbound

Bug fixes:
- route.uc: restrict hijack-dns to local DNS inbounds only (dns-in,
  source-dns-in, dns-health-*) — fixes socket collision between sing-box
  and dnsmasq on port 53 via SO_REUSEADDR transparent write-back sockets
- runtime.uc: add Port 53 socket collision diagnostic with auto-fix
  (sing-box restart to clear parasitic UNCONN sockets with Recv-Q)
- runtime.uc: add dnsmasq dns_redirect check with auto-fix — disables
  firewall4 DNAT that causes TProxy to rewrite destination to router :53
- failover.uc: add wdtt/olcrtc to failover candidate actions
- telegram.uc: show WDTT/OlcRTC installed status in /info

Tests: 135/155 shell + 591/591 frontend
This commit is contained in:
Dushnilin
2026-09-11 18:04:03 +03:00
parent 3e716b1d1b
commit 26431f50d3
48 changed files with 3392 additions and 39 deletions
@@ -3407,6 +3407,8 @@ var Tachyon;
AvailableMethods2["GET_TAILSCALE_PEERS"] = "get_tailscale_peers";
AvailableMethods2["GET_ZAPRET2_STATUS"] = "get_zapret2_status";
AvailableMethods2["GET_BYEDPI_STATUS"] = "get_byedpi_status";
AvailableMethods2["GET_WDTT_STATUS"] = "get_wdtt_status";
AvailableMethods2["GET_OLCRTC_STATUS"] = "get_olcrtc_status";
AvailableMethods2["CLASH_API"] = "clash_api";
AvailableMethods2["ENABLE"] = "enable";
AvailableMethods2["DISABLE"] = "disable";
@@ -3734,6 +3736,18 @@ var TachyonShellMethods = {
"/usr/bin/tachyon",
{ allowNonZeroWithStdout: true }
),
getWdttStatus: async () => callBaseMethod(
Tachyon.AvailableMethods.GET_WDTT_STATUS,
[],
"/usr/bin/tachyon",
{ allowNonZeroWithStdout: true }
),
getOlcrtcStatus: async () => callBaseMethod(
Tachyon.AvailableMethods.GET_OLCRTC_STATUS,
[],
"/usr/bin/tachyon",
{ allowNonZeroWithStdout: true }
),
getClashApiProxies: async () => callBaseMethod(Tachyon.AvailableMethods.CLASH_API, [
Tachyon.AvailableClashAPIMethods.GET_PROXIES
]),
@@ -5045,7 +5059,9 @@ function isConnectionAction(action) {
);
}
function isServiceAction(action) {
return Boolean(action && ["zapret", "zapret2", "byedpi"].includes(action));
return Boolean(
action && ["zapret", "zapret2", "byedpi", "wdtt", "olcrtc"].includes(action)
);
}
function hasSubscriptionSources(section) {
return getSubscriptionSourceCount(section) > 0;
@@ -5826,6 +5842,38 @@ async function getDashboardSections(options = {}) {
statusMessage: s.status_message
};
}
} else if (serviceType === "wdtt") {
const result = await TachyonShellMethods.getWdttStatus();
if (result.success && result.data) {
const s = result.data;
return {
serviceType: "wdtt",
configured: Boolean(s.configured),
ready: Boolean(s.ready),
conflict: Boolean(s.conflict),
runningProcesses: s.running_process_count,
expectedProcesses: s.expected_process_count,
restartCount: s.restart_count,
unstable: Boolean(s.runtime_unstable),
statusMessage: s.status_message
};
}
} else if (serviceType === "olcrtc") {
const result = await TachyonShellMethods.getOlcrtcStatus();
if (result.success && result.data) {
const s = result.data;
return {
serviceType: "olcrtc",
configured: Boolean(s.configured),
ready: Boolean(s.ready),
conflict: Boolean(s.conflict),
runningProcesses: s.running_process_count,
expectedProcesses: s.expected_process_count,
restartCount: s.restart_count,
unstable: Boolean(s.runtime_unstable),
statusMessage: s.status_message
};
}
}
} catch (_error) {
}
@@ -6227,6 +6275,14 @@ var initialDiagnosticStore = {
byedpi_installed: 0,
byedpi_backup_version: "",
byedpi_backup_time: 0,
wdtt_version: "loading",
wdtt_installed: 0,
wdtt_backup_version: "",
wdtt_backup_time: 0,
olcrtc_version: "loading",
olcrtc_installed: 0,
olcrtc_backup_version: "",
olcrtc_backup_time: 0,
tailscale_version: "loading",
tailscale_installed: 0,
tailscale_backup_version: "",
@@ -6307,6 +6363,14 @@ var initialDiagnosticStore = {
byedpiInstall: { loading: false },
byedpiRemove: { loading: false },
byedpiRollback: { loading: false },
wdttCheck: { loading: false },
wdttInstall: { loading: false },
wdttRemove: { loading: false },
wdttRollback: { loading: false },
olcrtcCheck: { loading: false },
olcrtcInstall: { loading: false },
olcrtcRemove: { loading: false },
olcrtcRollback: { loading: false },
tailscaleCheck: { loading: false },
tailscaleInstall: { loading: false },
tailscaleRemove: { loading: false },
@@ -6632,7 +6696,7 @@ function getTachyonLogNotification(line) {
return { kind: "error", line };
}
const update = line.match(
/\[component-update\]\s+(tachyon|sing_box|zapret|zapret2|byedpi)\s+(\S+)/i
/\[component-update\]\s+(tachyon|sing_box|zapret|zapret2|byedpi|wdtt|olcrtc)\s+(\S+)/i
);
if (!update) {
return null;
@@ -6697,6 +6761,16 @@ var componentActionKeyMap = {
"byedpi:install_version": "byedpiInstall",
"byedpi:remove": "byedpiRemove",
"byedpi:rollback": "byedpiRollback",
"wdtt:check_update": "wdttCheck",
"wdtt:install": "wdttInstall",
"wdtt:install_version": "wdttInstall",
"wdtt:remove": "wdttRemove",
"wdtt:rollback": "wdttRollback",
"olcrtc:check_update": "olcrtcCheck",
"olcrtc:install": "olcrtcInstall",
"olcrtc:install_version": "olcrtcInstall",
"olcrtc:remove": "olcrtcRemove",
"olcrtc:rollback": "olcrtcRollback",
"tailscale:check_update": "tailscaleCheck",
"tailscale:install": "tailscaleInstall",
"tailscale:install_version": "tailscaleInstall",
@@ -6872,6 +6946,14 @@ function getEmptyUpdatesActions() {
byedpiInstall: { loading: false },
byedpiRemove: { loading: false },
byedpiRollback: { loading: false },
wdttCheck: { loading: false },
wdttInstall: { loading: false },
wdttRemove: { loading: false },
wdttRollback: { loading: false },
olcrtcCheck: { loading: false },
olcrtcInstall: { loading: false },
olcrtcRemove: { loading: false },
olcrtcRollback: { loading: false },
tailscaleCheck: { loading: false },
tailscaleInstall: { loading: false },
tailscaleRemove: { loading: false },
@@ -6913,6 +6995,8 @@ function applyServiceState(uiState) {
zapret_installed: uiState.capabilities.zapret_installed,
zapret2_installed: uiState.capabilities.zapret2_installed,
byedpi_installed: uiState.capabilities.byedpi_installed,
wdtt_installed: uiState.capabilities.wdtt_installed,
olcrtc_installed: uiState.capabilities.olcrtc_installed,
server_inbounds_enabled_count: uiState.capabilities.server_inbounds_enabled_count
};
nextSystemInfo.sing_box_extended = uiState.capabilities.sing_box_extended;
@@ -7146,7 +7230,9 @@ function componentDisplayName(component) {
sing_box: "sing-box",
zapret: "Zapret",
zapret2: "Zapret2",
byedpi: "ByeDPI"
byedpi: "ByeDPI",
wdtt: "WDTT",
olcrtc: "OlcRTC"
};
return names[component] || component;
}
@@ -10767,6 +10853,7 @@ async function runZapret2Check() {
const tachyonRuntimeReady = !hasZapret2Rules || runningProcesses === expectedProcesses && supervisorProcesses === expectedProcesses;
const unexpectedRuntime = !hasZapret2Rules && (runningProcesses > 0 || supervisorProcesses > 0);
const outboundsConfigured = Boolean(data.outbounds_configured);
const routesConfigured = Boolean(data.routes_configured);
const standaloneServiceEnabled = Boolean(data.standalone_service_enabled);
const standaloneServiceRunning = Boolean(data.standalone_service_running);
const standaloneConflict = hasZapret2Rules && standaloneServiceRunning;
@@ -10802,6 +10889,11 @@ async function runZapret2Check() {
key: outboundsConfigured ? _("Zapret2 sing-box outbound is configured") : _("Zapret2 sing-box outbound is not configured"),
value: ""
},
{
state: !hasZapret2Rules || routesConfigured ? "success" : "error",
key: routesConfigured ? _("Zapret2 sing-box route rules are configured") : _("Zapret2 sing-box route rules are not configured"),
value: ""
},
{
state: standaloneConflict ? "error" : standaloneAutostartRisk ? "warning" : "success",
key: standaloneServiceRunning ? hasZapret2Rules ? _(
@@ -10925,6 +11017,10 @@ var UNKNOWN_SYSTEM_INFO = {
zapret2_installed: 0,
byedpi_version: _("unknown"),
byedpi_installed: 0,
wdtt_version: _("unknown"),
wdtt_installed: 0,
olcrtc_version: _("unknown"),
olcrtc_installed: 0,
tailscale_version: _("unknown"),
tailscale_installed: 0,
server_inbounds_enabled_count: -1,
@@ -10990,6 +11086,8 @@ async function ensureSystemInfo({
zapret_installed: latestSystemInfo.zapret_installed,
zapret2_installed: latestSystemInfo.zapret2_installed,
byedpi_installed: latestSystemInfo.byedpi_installed,
wdtt_installed: latestSystemInfo.wdtt_installed,
olcrtc_installed: latestSystemInfo.olcrtc_installed,
server_inbounds_enabled_count: latestSystemInfo.server_inbounds_enabled_count
};
store.set({
@@ -23,6 +23,8 @@ const ROUTING_ACTIONS = [
"zapret",
"zapret2",
"byedpi",
"wdtt",
"olcrtc",
"awg",
"warp",
"anytls",
@@ -487,6 +489,8 @@ const actionProvidersAvailabilityState = {
zapretInstalled: false,
zapret2Installed: false,
byedpiInstalled: false,
wdttInstalled: false,
olcrtcInstalled: false,
torInstalled: false,
singBoxExtended: false,
};
@@ -669,6 +673,18 @@ function updateActionProvidersAvailabilityState(nextState) {
);
}
if (typeof nextState.wdttInstalled !== "undefined") {
actionProvidersAvailabilityState.wdttInstalled = Boolean(
nextState.wdttInstalled,
);
}
if (typeof nextState.olcrtcInstalled !== "undefined") {
actionProvidersAvailabilityState.olcrtcInstalled = Boolean(
nextState.olcrtcInstalled,
);
}
if (typeof nextState.singBoxExtended !== "undefined") {
actionProvidersAvailabilityState.singBoxExtended = Boolean(
nextState.singBoxExtended,
@@ -693,6 +709,8 @@ function updateActionProvidersAvailabilityFromSystemInfo(systemInfo) {
zapretInstalled: Boolean(systemInfo.zapret_installed),
zapret2Installed: Boolean(systemInfo.zapret2_installed),
byedpiInstalled: Boolean(systemInfo.byedpi_installed),
wdttInstalled: Boolean(systemInfo.wdtt_installed),
olcrtcInstalled: Boolean(systemInfo.olcrtc_installed),
singBoxExtended: Boolean(systemInfo.sing_box_extended),
torInstalled: Boolean(systemInfo.tor_installed),
});
@@ -2102,6 +2120,14 @@ function isDownloadThroughTargetSection(section, currentSectionId) {
return isByedpiInstalledForUi();
}
if (action === "wdtt") {
return isWdttInstalledForUi();
}
if (action === "olcrtc") {
return isOlcrtcInstalledForUi();
}
return false;
}
@@ -4534,6 +4560,14 @@ function isByedpiInstalledForUi() {
return actionProvidersAvailabilityState.byedpiInstalled;
}
function isWdttInstalledForUi() {
return actionProvidersAvailabilityState.wdttInstalled;
}
function isOlcrtcInstalledForUi() {
return actionProvidersAvailabilityState.olcrtcInstalled;
}
function isSingBoxExtendedForUi() {
return actionProvidersAvailabilityState.singBoxExtended;
}
@@ -4565,6 +4599,10 @@ function getActionOptionLabel(action) {
return "Zapret2";
case "byedpi":
return "ByeDPI";
case "wdtt":
return "WDTT";
case "olcrtc":
return "OlcRTC";
case "awg":
return "AmneziaWG";
@@ -4607,6 +4645,14 @@ function getRuleActionDisplayValue(section_id) {
return "ByeDPI";
}
if (action === "wdtt") {
return "WDTT";
}
if (action === "olcrtc") {
return "OlcRTC";
}
if (action === "awg") {
return "AmneziaWG";
}
@@ -4673,6 +4719,12 @@ function populateActionOptionValues(option) {
if (isByedpiInstalledForUi()) {
option.value("byedpi", getActionOptionLabel("byedpi"));
}
if (isWdttInstalledForUi()) {
option.value("wdtt", getActionOptionLabel("wdtt"));
}
if (isOlcrtcInstalledForUi()) {
option.value("olcrtc", getActionOptionLabel("olcrtc"));
}
option.value("hosts", getActionOptionLabel("hosts"));
}
@@ -7819,6 +7871,227 @@ function createSectionContent(section) {
};
configureTextareaOption(o, analyzeByedpiStrategy);
// ─── WDTT Fields ──────────────────────────────────────────────────────
o = section.taboption(
"settings",
form.Value,
"warp_flow_uri",
_("WDTT URI"),
_("WDTT connection URI (wdtt://...)"),
);
o.depends("action", "wdtt");
o.modalonly = true;
o = section.taboption(
"settings",
form.ListValue,
"warp_flow_mode",
_("WDTT Mode"),
);
o.depends("action", "wdtt");
o.value("", _("Default"));
o.value("freedom", _("Freedom"));
o.value("captive", _("Captive"));
o.value("camou", _("Camou"));
o.modalonly = true;
o = section.taboption(
"settings",
form.Value,
"warp_flow_device_id",
_("WDTT Device ID"),
);
o.depends("action", "wdtt");
o.modalonly = true;
o = section.taboption(
"settings",
form.Value,
"warp_flow_workers",
_("WDTT Workers"),
);
o.depends("action", "wdtt");
o.modalonly = true;
o = section.taboption(
"settings",
form.Value,
"warp_flow_max_hashes",
_("WDTT Max Hashes"),
);
o.depends("action", "wdtt");
o.modalonly = true;
o = section.taboption(
"settings",
form.Value,
"warp_flow_mtu",
_("WDTT MTU"),
);
o.depends("action", "wdtt");
o.modalonly = true;
o = section.taboption(
"settings",
form.Value,
"warp_flow_refresh",
_("WDTT Refresh"),
);
o.depends("action", "wdtt");
o.modalonly = true;
o = section.taboption(
"settings",
form.Flag,
"warp_flow_auto_update",
_("WDTT Auto Update"),
);
o.default = "1";
o.depends("action", "wdtt");
o.modalonly = true;
o = section.taboption(
"settings",
form.Flag,
"warp_flow_block_doh",
_("WDTT Block DoH"),
);
o.default = "0";
o.depends("action", "wdtt");
o.modalonly = true;
o = section.taboption(
"settings",
form.Flag,
"warp_flow_block_ipv6",
_("WDTT Block IPv6"),
);
o.default = "0";
o.depends("action", "wdtt");
o.modalonly = true;
o = section.taboption(
"settings",
form.ListValue,
"warp_flow_qwdtt_mode",
_("WDTT QWDTT Mode"),
);
o.depends("action", "wdtt");
o.value("", _("Default"));
o.value("client", _("Client"));
o.value("server", _("Server"));
o.modalonly = true;
o = section.taboption(
"settings",
form.DynamicList,
"warp_flow_subscription_links",
_("WDTT Subscription Links"),
);
o.depends("action", "wdtt");
o.modalonly = true;
o = section.taboption(
"settings",
form.DynamicList,
"warp_flow_community_lists",
_("WDTT Community Lists"),
);
o.depends("action", "wdtt");
o.modalonly = true;
o = section.taboption(
"settings",
form.DynamicList,
"warp_flow_remote_domain_list",
_("WDTT Remote Domain List"),
);
o.depends("action", "wdtt");
o.modalonly = true;
// ─── OlcRTC Fields ────────────────────────────────────────────────────
o = section.taboption(
"settings",
form.ListValue,
"olcrtc_provider",
_("OlcRTC Provider"),
);
o.depends("action", "olcrtc");
o.value("jitsi", _("Jitsi"));
o.value("wrt", _("WRT"));
o.modalonly = true;
o = section.taboption(
"settings",
form.ListValue,
"olcrtc_transport",
_("OlcRTC Transport"),
);
o.depends("action", "olcrtc");
o.value("whep", _("WHEP"));
o.value("whip", _("WHIP"));
o.modalonly = true;
o = section.taboption(
"settings",
form.Value,
"olcrtc_room_id",
_("OlcRTC Room ID"),
);
o.depends("action", "olcrtc");
o.modalonly = true;
o = section.taboption(
"settings",
form.Value,
"olcrtc_crypto_key",
_("OlcRTC Crypto Key"),
);
o.depends("action", "olcrtc");
o.password = true;
o.modalonly = true;
o = section.taboption(
"settings",
form.Value,
"olcrtc_socks_host",
_("OlcRTC SOCKS Host"),
);
o.depends("action", "olcrtc");
o.default = "127.0.0.1";
o.modalonly = true;
o = section.taboption(
"settings",
form.Value,
"olcrtc_socks_port",
_("OlcRTC SOCKS Port"),
);
o.depends("action", "olcrtc");
o.default = "1082";
o.modalonly = true;
o = section.taboption(
"settings",
form.Value,
"olcrtc_dns_server",
_("OlcRTC DNS Server"),
);
o.depends("action", "olcrtc");
o.default = "8.8.8.8:53";
o.modalonly = true;
o = section.taboption(
"settings",
form.DynamicList,
"olcrtc_subscription_links",
_("OlcRTC Subscription Links"),
);
o.depends("action", "olcrtc");
o.modalonly = true;
// ─── Hosts Import Helpers ──────────────────────────────────────────────
function parseHostsFile(text, options) {
@@ -10333,6 +10606,8 @@ function createSectionContent(section) {
o.depends("action", "connection");
o.depends("action", "awg");
o.depends("action", "byedpi");
o.depends("action", "wdtt");
o.depends("action", "olcrtc");
o.depends("action", "zapret");
o.depends("action", "zapret2");
o.modalonly = true;
@@ -10814,11 +11089,47 @@ function createSectionContent(section) {
key: "excluded_ips",
label: _("Excluded devices"),
description: _(
"Traffic from these IP addresses will never be routed through this section, even if other conditions match.",
"Traffic from these IP addresses, CIDRs, or MAC addresses will never be routed through this section, even if other conditions match.",
),
});
dependsOnRoutingAction(excludedIpsOption);
const routedDnsEnabledOption = section.taboption(
"conditions",
form.Flag,
"routed_dns_enabled",
_("Routed DNS"),
_("Resolve matched domains through an encrypted DNS server of this section instead of FakeIP. DNS queries traverse the same proxy as the section's outbound."),
);
dependsOnRoutingAction(routedDnsEnabledOption);
routedDnsEnabledOption.default = "0";
routedDnsEnabledOption.rmempty = false;
routedDnsEnabledOption.modalonly = true;
const routedDnsTypeOption = section.taboption(
"conditions",
form.ListValue,
"routed_dns_type",
_("Routed DNS Protocol"),
_("Protocol for the routed DNS resolver."),
);
routedDnsTypeOption.depends({ routed_dns_enabled: "1" });
routedDnsTypeOption.rmempty = false;
routedDnsTypeOption.modalonly = true;
routedDnsTypeOption.value("udp", "UDP");
routedDnsTypeOption.value("dot", "DNS over TLS");
routedDnsTypeOption.value("doh", "DNS over HTTPS");
routedDnsTypeOption.value("doq", "DNS over QUIC");
const routedDnsServerOption = addDynamicConditionField(section, {
key: "routed_dns_server",
label: _("Routed DNS Servers"),
description: _(
"DNS server addresses for domain resolution in this section (e.g. 1.1.1.1, https://cloudflare-dns.com/dns-query).",
),
});
routedDnsServerOption.depends({ routed_dns_enabled: "1" });
const portsOption = addDynamicConditionField(section, {
key: "ports",
label: _("Ports"),
@@ -10914,6 +11225,8 @@ const ACTION_COLORS = {
zapret: "#8e44ad",
zapret2: "#9b59b6",
byedpi: "#d35400",
wdtt: "#27ae60",
olcrtc: "#2980b9",
awg: "#008080",
warp: "#e67e22",
anytls: "#16a085",