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
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -13214,8 +13214,8 @@
]
},
{
"call": "Traffic from these IP addresses will never be routed through this section, even if other conditions match.",
"key": "Traffic from these IP addresses will never be routed through this section, even if other conditions match.",
"call": "Traffic from these IP addresses, CIDRs, or MAC addresses will never be routed through this section, even if other conditions match.",
"key": "Traffic from these IP addresses, CIDRs, or MAC addresses will never be routed through this section, even if other conditions match.",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:10821"
]
+76 -1
View File
@@ -1329,6 +1329,81 @@ msgstr ""
msgid "ByeDPI strategy cannot be empty"
msgstr ""
msgid "WDTT"
msgstr ""
msgid "WDTT URI"
msgstr ""
msgid "WDTT connection URI (wdtt://...)"
msgstr ""
msgid "WDTT Mode"
msgstr ""
msgid "WDTT Device ID"
msgstr ""
msgid "WDTT Workers"
msgstr ""
msgid "WDTT Max Hashes"
msgstr ""
msgid "WDTT MTU"
msgstr ""
msgid "WDTT Refresh"
msgstr ""
msgid "WDTT Auto Update"
msgstr ""
msgid "WDTT Block DoH"
msgstr ""
msgid "WDTT Block IPv6"
msgstr ""
msgid "WDTT QWDTT Mode"
msgstr ""
msgid "WDTT Subscription Links"
msgstr ""
msgid "WDTT Community Lists"
msgstr ""
msgid "WDTT Remote Domain List"
msgstr ""
msgid "OlcRTC"
msgstr ""
msgid "OlcRTC Provider"
msgstr ""
msgid "OlcRTC Transport"
msgstr ""
msgid "OlcRTC Room ID"
msgstr ""
msgid "OlcRTC Crypto Key"
msgstr ""
msgid "OlcRTC SOCKS Host"
msgstr ""
msgid "OlcRTC SOCKS Port"
msgstr ""
msgid "OlcRTC DNS Server"
msgstr ""
msgid "OlcRTC Subscription Links"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:14975
#: src/tachyon/tabs/diagnostic/partials/renderLeakCheckModal.ts:170
msgid "Bypasses proxy (SO_MARK 0x08000000)"
@@ -8155,7 +8230,7 @@ msgid "Traffic for this level goes directly."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:10821
msgid "Traffic from these IP addresses will never be routed through this section, even if other conditions match."
msgid "Traffic from these IP addresses, CIDRs, or MAC addresses will never be routed through this section, even if other conditions match."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:10849
+95 -2
View File
@@ -839,6 +839,81 @@ msgstr "Стратегия ByeDPI"
msgid "ByeDPI strategy cannot be empty"
msgstr "Стратегия ByeDPI не может быть пустой"
msgid "WDTT"
msgstr "WDTT"
msgid "WDTT URI"
msgstr "URI WDTT"
msgid "WDTT connection URI (wdtt://...)"
msgstr "URI подключения WDTT (wdtt://...)"
msgid "WDTT Mode"
msgstr "Режим WDTT"
msgid "WDTT Device ID"
msgstr "ID устройства WDTT"
msgid "WDTT Workers"
msgstr "Рабочие процессы WDTT"
msgid "WDTT Max Hashes"
msgstr "Максимум хешей WDTT"
msgid "WDTT MTU"
msgstr "MTU WDTT"
msgid "WDTT Refresh"
msgstr "Обновление WDTT"
msgid "WDTT Auto Update"
msgstr "Автообновление WDTT"
msgid "WDTT Block DoH"
msgstr "Блокировка DoH в WDTT"
msgid "WDTT Block IPv6"
msgstr "Блокировка IPv6 в WDTT"
msgid "WDTT QWDTT Mode"
msgstr "Режим QWDTT"
msgid "WDTT Subscription Links"
msgstr "Ссылки подписок WDTT"
msgid "WDTT Community Lists"
msgstr "Списки сообществ WDTT"
msgid "WDTT Remote Domain List"
msgstr "Удалённый список доменов WDTT"
msgid "OlcRTC"
msgstr "OlcRTC"
msgid "OlcRTC Provider"
msgstr "Провайдер OlcRTC"
msgid "OlcRTC Transport"
msgstr "Транспорт OlcRTC"
msgid "OlcRTC Room ID"
msgstr "ID комнаты OlcRTC"
msgid "OlcRTC Crypto Key"
msgstr "Ключ шифрования OlcRTC"
msgid "OlcRTC SOCKS Host"
msgstr "Хост SOCKS OlcRTC"
msgid "OlcRTC SOCKS Port"
msgstr "Порт SOCKS OlcRTC"
msgid "OlcRTC DNS Server"
msgstr "DNS-сервер OlcRTC"
msgid "OlcRTC Subscription Links"
msgstr "Ссылки подписок OlcRTC"
msgid "Bypasses proxy (SO_MARK 0x08000000)"
msgstr "В обход прокси (SO_MARK 0x08000000)"
@@ -1445,6 +1520,24 @@ msgstr "DNS через прокси"
msgid "DNS through section"
msgstr "DNS через секцию"
msgid "Routed DNS"
msgstr "Маршрутизированный DNS"
msgid "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."
msgstr "Резолвить совпавшие домены через зашифрованный DNS-сервер этой секции вместо FakeIP. DNS-запросы проходят через тот же прокси, что и исходящий трафик секции."
msgid "Routed DNS Protocol"
msgstr "Протокол маршрутизированного DNS"
msgid "Protocol for the routed DNS resolver."
msgstr "Протокол для DNS-резолвера маршрутизации."
msgid "Routed DNS Servers"
msgstr "Серверы маршрутизированного DNS"
msgid "DNS server addresses for domain resolution in this section (e.g. 1.1.1.1, https://cloudflare-dns.com/dns-query)."
msgstr "Адреса DNS-серверов для резолвинга доменов в этой секции (напр. 1.1.1.1, https://cloudflare-dns.com/dns-query)."
msgid "DNS Turbo Cache"
msgstr "Быстрый DNS-кэш"
@@ -5096,8 +5189,8 @@ msgstr "Трафик"
msgid "Traffic for this level goes directly."
msgstr "Трафик этого уровня идёт напрямую."
msgid "Traffic from these IP addresses will never be routed through this section, even if other conditions match."
msgstr "Трафик от этих IP-адресов никогда не будет маршрутизирован через эту секцию, даже если другие условия совпадают."
msgid "Traffic from these IP addresses, CIDRs, or MAC addresses will never be routed through this section, even if other conditions match."
msgstr "Трафик от этих IP-адресов, CIDR или MAC-адресов никогда не будет маршрутизирован через эту секцию, даже если другие условия совпадают."
msgid "Traffic of specified protocols (e.g. BitTorrent, QUIC, STUN) will bypass this section."
msgstr "Трафик указанных протоколов (например BitTorrent, QUIC, STUN) будет обходить эту секцию."
@@ -33,6 +33,16 @@ const componentActionKeyMap: Record<string, UpdatesActionKey> = {
'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',
@@ -415,7 +415,10 @@ function isConnectionAction(action?: string) {
}
function isServiceAction(action?: string) {
return Boolean(action && ['zapret', 'zapret2', 'byedpi'].includes(action));
return Boolean(
action &&
['zapret', 'zapret2', 'byedpi', 'wdtt', 'olcrtc'].includes(action),
);
}
function hasSubscriptionSources(section: Tachyon.ConfigSection) {
@@ -1517,7 +1520,9 @@ export async function getDashboardSections(
Promise<Tachyon.ServiceStatus | undefined>
>();
const getServiceStatus = (serviceType: 'zapret' | 'zapret2' | 'byedpi') => {
const getServiceStatus = (
serviceType: 'zapret' | 'zapret2' | 'byedpi' | 'wdtt' | 'olcrtc',
) => {
if (!serviceStatusCache.has(serviceType)) {
serviceStatusCache.set(
serviceType,
@@ -1571,6 +1576,38 @@ export async function getDashboardSections(
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) {
// Status fetch failed — show section without status
@@ -316,6 +316,20 @@ export const TachyonShellMethods = {
'/usr/bin/tachyon',
{ allowNonZeroWithStdout: true },
),
getWdttStatus: async () =>
callBaseMethod<Tachyon.GetWdttStatus>(
Tachyon.AvailableMethods.GET_WDTT_STATUS,
[],
'/usr/bin/tachyon',
{ allowNonZeroWithStdout: true },
),
getOlcrtcStatus: async () =>
callBaseMethod<Tachyon.GetOlcrtcStatus>(
Tachyon.AvailableMethods.GET_OLCRTC_STATUS,
[],
'/usr/bin/tachyon',
{ allowNonZeroWithStdout: true },
),
getClashApiProxies: async () =>
callBaseMethod<ClashAPI.Proxies>(Tachyon.AvailableMethods.CLASH_API, [
Tachyon.AvailableClashAPIMethods.GET_PROXIES,
@@ -28,6 +28,8 @@ function componentDisplayName(component: string) {
zapret: 'Zapret',
zapret2: 'Zapret2',
byedpi: 'ByeDPI',
wdtt: 'WDTT',
olcrtc: 'OlcRTC',
};
return names[component] || component;
@@ -86,7 +86,7 @@ export function getTachyonLogNotification(
}
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) {
@@ -233,6 +233,14 @@ export interface StoreType {
byedpi_installed: number;
byedpi_backup_version?: string;
byedpi_backup_time?: number;
wdtt_version: string;
wdtt_installed: number;
wdtt_backup_version?: string;
wdtt_backup_time?: number;
olcrtc_version: string;
olcrtc_installed: number;
olcrtc_backup_version?: string;
olcrtc_backup_time?: number;
tailscale_version: string;
tailscale_installed: number;
tailscale_backup_version?: string;
@@ -273,6 +281,14 @@ export interface StoreType {
byedpiInstall: { loading: boolean };
byedpiRemove: { loading: boolean };
byedpiRollback: { loading: boolean };
wdttCheck: { loading: boolean };
wdttInstall: { loading: boolean };
wdttRemove: { loading: boolean };
wdttRollback: { loading: boolean };
olcrtcCheck: { loading: boolean };
olcrtcInstall: { loading: boolean };
olcrtcRemove: { loading: boolean };
olcrtcRollback: { loading: boolean };
tailscaleCheck: { loading: boolean };
tailscaleInstall: { loading: boolean };
tailscaleRemove: { loading: boolean };
@@ -24,6 +24,10 @@ const UNKNOWN_SYSTEM_INFO: StoreType['diagnosticsSystemInfo'] = {
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,
@@ -108,6 +112,8 @@ export 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,
};
@@ -43,6 +43,8 @@ function createUiState(
zapret_installed: 1,
zapret2_installed: 1,
byedpi_installed: 0,
wdtt_installed: 0,
olcrtc_installed: 0,
server_inbounds_enabled_count: 0,
},
actions: {
@@ -37,6 +37,8 @@ function createUiState(
zapret_installed: 1,
zapret2_installed: 0,
byedpi_installed: 1,
wdtt_installed: 0,
olcrtc_installed: 0,
server_inbounds_enabled_count: 0,
...capabilities,
},
@@ -37,6 +37,14 @@ function getEmptyUpdatesActions(): StoreType['updatesActions'] {
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 },
@@ -88,6 +96,8 @@ function applyServiceState(uiState: Tachyon.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,
};
@@ -102,6 +102,14 @@ export const initialDiagnosticStore: Pick<
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: '',
@@ -182,6 +190,14 @@ export const initialDiagnosticStore: Pick<
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 },
+87 -1
View File
@@ -69,6 +69,8 @@ export namespace Tachyon {
GET_TAILSCALE_PEERS = 'get_tailscale_peers',
GET_ZAPRET2_STATUS = 'get_zapret2_status',
GET_BYEDPI_STATUS = 'get_byedpi_status',
GET_WDTT_STATUS = 'get_wdtt_status',
GET_OLCRTC_STATUS = 'get_olcrtc_status',
CLASH_API = 'clash_api',
ENABLE = 'enable',
DISABLE = 'disable',
@@ -200,7 +202,7 @@ export namespace Tachyon {
}
export interface ServiceStatus {
serviceType: 'zapret' | 'zapret2' | 'byedpi';
serviceType: 'zapret' | 'zapret2' | 'byedpi' | 'wdtt' | 'olcrtc';
configured: boolean;
ready: boolean;
conflict: boolean;
@@ -275,6 +277,8 @@ export namespace Tachyon {
| 'zapret'
| 'zapret2'
| 'byedpi'
| 'wdtt'
| 'olcrtc'
| 'awg'
| 'warp'
| 'anytls'
@@ -321,6 +325,9 @@ export namespace Tachyon {
ports?: string[];
protocol?: string[];
excluded_protocol?: string[];
routed_dns_enabled?: '0' | '1';
routed_dns_type?: 'udp' | 'dot' | 'doh' | 'doq';
routed_dns_server?: string | string[];
update_interval?: string;
proxy_string?: string;
@@ -328,6 +335,31 @@ export namespace Tachyon {
nfqws2_opt?: string;
byedpi_cmd_opts?: string;
cmd_opts?: string;
warp_flow_uri?: string;
warp_flow_mode?: string;
warp_flow_device_id?: string;
warp_flow_workers?: string;
warp_flow_max_hashes?: string;
warp_flow_mtu?: string;
warp_flow_refresh?: string;
warp_flow_auto_update?: boolean;
warp_flow_block_doh?: boolean;
warp_flow_block_ipv6?: boolean;
warp_flow_subscription_links?: string[];
warp_flow_community_lists?: string[];
warp_flow_remote_domain_list?: string[];
warp_flow_qwdtt_mode?: string;
olcrtc_provider?: string;
olcrtc_transport?: string;
olcrtc_room_id?: string;
olcrtc_crypto_key?: string;
olcrtc_socks_host?: string;
olcrtc_socks_port?: string;
olcrtc_dns_server?: string;
olcrtc_subscription_links?: string[];
selector_proxy_links?: string[];
subscription_urls?: string[];
interfaces?: string[];
@@ -610,6 +642,10 @@ export namespace Tachyon {
zapret2_installed: 0 | 1;
byedpi_version: string;
byedpi_installed: 0 | 1;
wdtt_version: string;
wdtt_installed: 0 | 1;
olcrtc_version: string;
olcrtc_installed: 0 | 1;
tailscale_version: string;
tailscale_installed: 0 | 1;
direct_bypass_enabled?: 0 | 1;
@@ -639,6 +675,8 @@ export namespace Tachyon {
zapret_installed: 0 | 1;
zapret2_installed: 0 | 1;
byedpi_installed: 0 | 1;
wdtt_installed: 0 | 1;
olcrtc_installed: 0 | 1;
server_inbounds_enabled_count: number;
}
@@ -887,6 +925,54 @@ export namespace Tachyon {
status_message: string;
}
export interface GetWdttStatus {
installed: 0 | 1;
package_installed: 0 | 1;
provider_available: 0 | 1;
provider_path: string;
version: string;
configured: 0 | 1;
enabled_rule_count: number;
expected_process_count: number;
running_process_count: number;
supervisor_process_count: number;
restart_count: number;
runtime_unstable: 0 | 1;
standalone_service_enabled: 0 | 1;
standalone_service_running: 0 | 1;
listen_address: string;
port_base: number;
outbounds_configured: 0 | 1;
routes_configured: 0 | 1;
ready: 0 | 1;
conflict: 0 | 1;
status_message: string;
}
export interface GetOlcrtcStatus {
installed: 0 | 1;
package_installed: 0 | 1;
provider_available: 0 | 1;
provider_path: string;
version: string;
configured: 0 | 1;
enabled_rule_count: number;
expected_process_count: number;
running_process_count: number;
supervisor_process_count: number;
restart_count: number;
runtime_unstable: 0 | 1;
standalone_service_enabled: 0 | 1;
standalone_service_running: 0 | 1;
listen_address: string;
port_base: number;
outbounds_configured: 0 | 1;
routes_configured: 0 | 1;
ready: 0 | 1;
conflict: 0 | 1;
status_message: string;
}
export interface ByedpiCheckResult {
byedpi_installed: 0 | 1;
byedpi_package_installed: 0 | 1;