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:
File diff suppressed because one or more lines are too long
@@ -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"
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) будет обходить эту секцию."
|
||||
|
||||
@@ -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)"
|
||||
@@ -2355,6 +2430,24 @@ msgstr ""
|
||||
msgid "DNS through section"
|
||||
msgstr ""
|
||||
|
||||
msgid "Routed DNS"
|
||||
msgstr ""
|
||||
|
||||
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 ""
|
||||
|
||||
msgid "Routed DNS Protocol"
|
||||
msgstr ""
|
||||
|
||||
msgid "Protocol for the routed DNS resolver."
|
||||
msgstr ""
|
||||
|
||||
msgid "Routed DNS Servers"
|
||||
msgstr ""
|
||||
|
||||
msgid "DNS server addresses for domain resolution in this section (e.g. 1.1.1.1, https://cloudflare-dns.com/dns-query)."
|
||||
msgstr ""
|
||||
|
||||
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:1615
|
||||
msgid "DNS Turbo Cache"
|
||||
msgstr ""
|
||||
@@ -8155,7 +8248,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
|
||||
|
||||
@@ -112,6 +112,8 @@ function show_help() {
|
||||
print(" get_zapret_status Get zapret provider and Tachyon-managed nfqws status\n");
|
||||
print(" get_zapret2_status Get zapret2 provider and Tachyon-managed nfqws2 status\n");
|
||||
print(" get_byedpi_status Get ByeDPI provider and Tachyon-managed ciadpi status\n");
|
||||
print(" get_wdtt_status Get WDTT provider and Tachyon-managed wdtt status\n");
|
||||
print(" get_olcrtc_status Get OlcRTC provider and Tachyon-managed olcrtc status\n");
|
||||
print(" get_system_info Get system information in JSON format\n");
|
||||
print(" get_server_capabilities Get server UI capabilities in JSON format\n");
|
||||
print(" get_ui_capabilities Get lightweight UI capabilities in JSON format\n");
|
||||
@@ -226,6 +228,8 @@ function command_spec(command) {
|
||||
get_zapret_status: [ "diagnostics/runtime.uc", "get-zapret-status", 0 ],
|
||||
get_zapret2_status: [ "diagnostics/runtime.uc", "get-zapret2-status", 0 ],
|
||||
get_byedpi_status: [ "diagnostics/runtime.uc", "get-byedpi-status", 0 ],
|
||||
get_wdtt_status: [ "diagnostics/runtime.uc", "get-wdtt-status", 0 ],
|
||||
get_olcrtc_status: [ "diagnostics/runtime.uc", "get-olcrtc-status", 0 ],
|
||||
get_tailscale_status: [ "diagnostics/runtime.uc", "get-tailscale-status", 0 ],
|
||||
get_tailscale_peers: [ "diagnostics/runtime.uc", "get-tailscale-peers", 0 ],
|
||||
tailscale_restart: [ "providers/tailscale/runtime.uc", "start-runtime", 0 ],
|
||||
|
||||
@@ -1349,6 +1349,74 @@ function resolve_byedpi_release(arch, tag) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolve_wdtt_release(arch, tag) {
|
||||
let asset_ext = is_apk() ? "apk" : "ipk";
|
||||
let release_series = trim(helper_output("openwrt-release-series", [ "/etc/openwrt_release" ]));
|
||||
let releases_json = (tag != null && tag != "") ?
|
||||
fetch_github_release_by_tag_json("ret_dev", "wdtt", tag) :
|
||||
fetch_github_releases_json("ret_dev", "wdtt", "30");
|
||||
if (releases_json != "") {
|
||||
let resolved = trim(helper_output_input(releases_json, "wdtt-select-asset", [ release_series, asset_ext, arch.candidates ]));
|
||||
let fields = split(resolved, "\t");
|
||||
if (length(fields) >= 4) {
|
||||
return {
|
||||
arch: fields[0],
|
||||
package_name: fields[1],
|
||||
package_url: fields[2],
|
||||
release_url: fields[3],
|
||||
version: extract_arch_package_version(fields[1], fields[0])
|
||||
};
|
||||
}
|
||||
}
|
||||
if (tag != null && tag != "") {
|
||||
let distrib_arch = read_openwrt_release_value("DISTRIB_ARCH");
|
||||
let tag_clean = replace(tag, /^v/, "");
|
||||
let pkg_name = "wdtt_" + tag_clean + "_openwrt_" + distrib_arch + "." + asset_ext;
|
||||
return {
|
||||
arch: distrib_arch,
|
||||
package_name: pkg_name,
|
||||
package_url: "https://github.com/ret_dev/wdtt/releases/download/" + tag + "/" + pkg_name,
|
||||
release_url: "https://github.com/ret_dev/wdtt/releases/tag/" + tag,
|
||||
version: tag
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolve_olcrtc_release(arch, tag) {
|
||||
let asset_ext = is_apk() ? "apk" : "ipk";
|
||||
let release_series = trim(helper_output("openwrt-release-series", [ "/etc/openwrt_release" ]));
|
||||
let releases_json = (tag != null && tag != "") ?
|
||||
fetch_github_release_by_tag_json("ret_dev", "olcrtc", tag) :
|
||||
fetch_github_releases_json("ret_dev", "olcrtc", "30");
|
||||
if (releases_json != "") {
|
||||
let resolved = trim(helper_output_input(releases_json, "olcrtc-select-asset", [ release_series, asset_ext, arch.candidates ]));
|
||||
let fields = split(resolved, "\t");
|
||||
if (length(fields) >= 4) {
|
||||
return {
|
||||
arch: fields[0],
|
||||
package_name: fields[1],
|
||||
package_url: fields[2],
|
||||
release_url: fields[3],
|
||||
version: extract_arch_package_version(fields[1], fields[0])
|
||||
};
|
||||
}
|
||||
}
|
||||
if (tag != null && tag != "") {
|
||||
let distrib_arch = read_openwrt_release_value("DISTRIB_ARCH");
|
||||
let tag_clean = replace(tag, /^v/, "");
|
||||
let pkg_name = "olcrtc_" + tag_clean + "_openwrt_" + distrib_arch + "." + asset_ext;
|
||||
return {
|
||||
arch: distrib_arch,
|
||||
package_name: pkg_name,
|
||||
package_url: "https://github.com/ret_dev/olcrtc/releases/download/" + tag + "/" + pkg_name,
|
||||
release_url: "https://github.com/ret_dev/olcrtc/releases/tag/" + tag,
|
||||
version: tag
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function download_direct_package(release) {
|
||||
let package_file = tmp_dir + "/" + release.package_name;
|
||||
if (!download_with_retry(release.package_url, package_file, release.package_name) || !file_nonempty(package_file))
|
||||
@@ -1540,6 +1608,86 @@ function install_byedpi(action, target_tag) {
|
||||
action_success("byedpi", action, "ByeDPI package has been installed", current_version, pkg.version, 1, "latest", release.release_url || "");
|
||||
}
|
||||
|
||||
function install_wdtt(action, target_tag) {
|
||||
init_tmp_dir() || action_fail("wdtt", action, "Failed to create temporary directory");
|
||||
let arch = resolve_arch_candidates();
|
||||
if (arch == null)
|
||||
action_fail("wdtt", action, "Failed to detect package architecture");
|
||||
let release = null;
|
||||
retry_resolve("Resolving WDTT package", function() {
|
||||
release = resolve_wdtt_release(arch, target_tag);
|
||||
return release != null;
|
||||
});
|
||||
if (release == null)
|
||||
action_fail("wdtt", action, "Failed to resolve WDTT package for this router architecture");
|
||||
|
||||
let runtime_module = LIB_DIR + "/providers/wdtt/runtime.uc";
|
||||
let installed = provider_installed(runtime_module);
|
||||
let current_version = provider_package_version(runtime_module);
|
||||
if (action == "check_update") {
|
||||
if (!installed)
|
||||
action_success("wdtt", action, "WDTT is not installed", current_version, release.version, 0, "", release.release_url || "");
|
||||
check_success("wdtt", current_version, release.version, release.release_url || "");
|
||||
}
|
||||
|
||||
let pkg = download_direct_package(release);
|
||||
if (pkg == null)
|
||||
action_fail("wdtt", action, "Failed to download WDTT package");
|
||||
|
||||
run_logged("Updating package lists before WDTT package installation", pkg_list_update_command());
|
||||
|
||||
if (!run_logged("Installing WDTT package " + pkg.name, pkg_install_files_command([ pkg.file ])))
|
||||
action_fail("wdtt", action, "Failed to install WDTT package", current_version, pkg.version);
|
||||
|
||||
disable_standalone_service("wdtt");
|
||||
restart_tachyon_after_successful_change();
|
||||
clear_version_caches();
|
||||
current_version = provider_package_version(runtime_module);
|
||||
if (current_version == "")
|
||||
current_version = pkg.version || "unknown";
|
||||
action_success("wdtt", action, "WDTT package has been installed", current_version, pkg.version, 1, "latest", release.release_url || "");
|
||||
}
|
||||
|
||||
function install_olcrtc(action, target_tag) {
|
||||
init_tmp_dir() || action_fail("olcrtc", action, "Failed to create temporary directory");
|
||||
let arch = resolve_arch_candidates();
|
||||
if (arch == null)
|
||||
action_fail("olcrtc", action, "Failed to detect package architecture");
|
||||
let release = null;
|
||||
retry_resolve("Resolving OlcRTC package", function() {
|
||||
release = resolve_olcrtc_release(arch, target_tag);
|
||||
return release != null;
|
||||
});
|
||||
if (release == null)
|
||||
action_fail("olcrtc", action, "Failed to resolve OlcRTC package for this router architecture");
|
||||
|
||||
let runtime_module = LIB_DIR + "/providers/olcrtc/runtime.uc";
|
||||
let installed = provider_installed(runtime_module);
|
||||
let current_version = provider_package_version(runtime_module);
|
||||
if (action == "check_update") {
|
||||
if (!installed)
|
||||
action_success("olcrtc", action, "OlcRTC is not installed", current_version, release.version, 0, "", release.release_url || "");
|
||||
check_success("olcrtc", current_version, release.version, release.release_url || "");
|
||||
}
|
||||
|
||||
let pkg = download_direct_package(release);
|
||||
if (pkg == null)
|
||||
action_fail("olcrtc", action, "Failed to download OlcRTC package");
|
||||
|
||||
run_logged("Updating package lists before OlcRTC package installation", pkg_list_update_command());
|
||||
|
||||
if (!run_logged("Installing OlcRTC package " + pkg.name, pkg_install_files_command([ pkg.file ])))
|
||||
action_fail("olcrtc", action, "Failed to install OlcRTC package", current_version, pkg.version);
|
||||
|
||||
disable_standalone_service("olcrtc");
|
||||
restart_tachyon_after_successful_change();
|
||||
clear_version_caches();
|
||||
current_version = provider_package_version(runtime_module);
|
||||
if (current_version == "")
|
||||
current_version = pkg.version || "unknown";
|
||||
action_success("olcrtc", action, "OlcRTC package has been installed", current_version, pkg.version, 1, "latest", release.release_url || "");
|
||||
}
|
||||
|
||||
function install_tailscale(action) {
|
||||
let component = "tailscale";
|
||||
let label = "Tailscale";
|
||||
@@ -3115,6 +3263,26 @@ function rollback_component(component) {
|
||||
restart_tachyon_after_successful_change();
|
||||
action_success("byedpi", "rollback", "ByeDPI rolled back to " + backup_version, backup_version, "", 1);
|
||||
}
|
||||
else if (component == "wdtt") {
|
||||
let backup_bin = bdir + "/wdtt";
|
||||
if (!file_exists(backup_bin)) action_fail("wdtt", "rollback", "WDTT backup binary is missing");
|
||||
remove_file("/usr/bin/wdtt");
|
||||
command_success_from_args([ "cp", "-p", backup_bin, "/usr/bin/wdtt" ]);
|
||||
command_success_from_args([ "chmod", "0755", "/usr/bin/wdtt" ]);
|
||||
clear_version_caches();
|
||||
restart_tachyon_after_successful_change();
|
||||
action_success("wdtt", "rollback", "WDTT rolled back to " + backup_version, backup_version, "", 1);
|
||||
}
|
||||
else if (component == "olcrtc") {
|
||||
let backup_bin = bdir + "/olcrtc";
|
||||
if (!file_exists(backup_bin)) action_fail("olcrtc", "rollback", "OlcRTC backup binary is missing");
|
||||
remove_file("/usr/bin/olcrtc");
|
||||
command_success_from_args([ "cp", "-p", backup_bin, "/usr/bin/olcrtc" ]);
|
||||
command_success_from_args([ "chmod", "0755", "/usr/bin/olcrtc" ]);
|
||||
clear_version_caches();
|
||||
restart_tachyon_after_successful_change();
|
||||
action_success("olcrtc", "rollback", "OlcRTC rolled back to " + backup_version, backup_version, "", 1);
|
||||
}
|
||||
else if (component == "zapret") {
|
||||
let backup_bin = bdir + "/nfqws";
|
||||
if (!file_exists(backup_bin)) action_fail("zapret", "rollback", "Zapret backup binary is missing");
|
||||
@@ -3380,6 +3548,14 @@ function component_action(component, action, extra) {
|
||||
install_byedpi(action);
|
||||
else if (component == "byedpi" && action == "remove")
|
||||
remove_optional_component("byedpi", "byedpi", "ByeDPI", LIB_DIR + "/providers/byedpi/runtime.uc");
|
||||
else if (component == "wdtt" && (action == "check_update" || action == "install"))
|
||||
install_wdtt(action);
|
||||
else if (component == "wdtt" && action == "remove")
|
||||
remove_optional_component("wdtt", "wdtt", "WDTT", LIB_DIR + "/providers/wdtt/runtime.uc");
|
||||
else if (component == "olcrtc" && (action == "check_update" || action == "install"))
|
||||
install_olcrtc(action);
|
||||
else if (component == "olcrtc" && action == "remove")
|
||||
remove_optional_component("olcrtc", "olcrtc", "OlcRTC", LIB_DIR + "/providers/olcrtc/runtime.uc");
|
||||
else if (component == "tailscale" && (action == "check_update" || action == "install"))
|
||||
install_tailscale(action);
|
||||
else if (component == "tailscale" && action == "remove")
|
||||
|
||||
@@ -414,7 +414,7 @@ function openwrt_release_series(path) {
|
||||
|
||||
function updates_arch_package_version(package_name, package_arch) {
|
||||
let version = str_remove_suffix(str_remove_suffix(package_name, ".ipk"), ".apk");
|
||||
let prefixes = ["zapret2_", "zapret2-", "zapret_", "zapret-", "byedpi_", "byedpi-"];
|
||||
let prefixes = ["zapret2_", "zapret2-", "zapret_", "zapret-", "byedpi_", "byedpi-", "wdtt_", "wdtt-", "olcrtc_", "olcrtc-"];
|
||||
|
||||
for (let prefix in prefixes) {
|
||||
if (str_startswith(version, prefix)) {
|
||||
@@ -1037,6 +1037,78 @@ function byedpi_select_asset(series, asset_ext, arch_candidates) {
|
||||
}
|
||||
}
|
||||
|
||||
function wdtt_select_asset(series, asset_ext, arch_candidates) {
|
||||
let releases = releases_array_or_wrapped(read_stdin_json());
|
||||
|
||||
for (let pass = 0; pass < 2; pass++) {
|
||||
if (pass == 0 && as_string(series) == "")
|
||||
continue;
|
||||
|
||||
for (let release in releases) {
|
||||
if (type(release) != "object")
|
||||
continue;
|
||||
if (release.draft === true || (length(releases) > 1 && release.prerelease === true))
|
||||
continue;
|
||||
if (pass == 0 && length(releases) > 1) {
|
||||
let tag = as_string(release.tag_name || "");
|
||||
let name = as_string(release.name || "");
|
||||
if (!str_contains(tag, series) && !str_contains(name, series))
|
||||
continue;
|
||||
}
|
||||
for (let arch in split(as_string(arch_candidates), " ")) {
|
||||
if (arch == "")
|
||||
continue;
|
||||
for (let asset in array_or_empty(release.assets)) {
|
||||
if (type(asset) != "object")
|
||||
continue;
|
||||
let name = as_string(asset.name || "");
|
||||
let url = as_string(asset.browser_download_url || "");
|
||||
if (url != "" && release_asset_matches_arch(name, "wdtt", arch, asset_ext)) {
|
||||
print(arch, "\t", name, "\t", url, "\t", as_string(release.html_url || ""), "\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function olcrtc_select_asset(series, asset_ext, arch_candidates) {
|
||||
let releases = releases_array_or_wrapped(read_stdin_json());
|
||||
|
||||
for (let pass = 0; pass < 2; pass++) {
|
||||
if (pass == 0 && as_string(series) == "")
|
||||
continue;
|
||||
|
||||
for (let release in releases) {
|
||||
if (type(release) != "object")
|
||||
continue;
|
||||
if (release.draft === true || (length(releases) > 1 && release.prerelease === true))
|
||||
continue;
|
||||
if (pass == 0 && length(releases) > 1) {
|
||||
let tag = as_string(release.tag_name || "");
|
||||
let name = as_string(release.name || "");
|
||||
if (!str_contains(tag, series) && !str_contains(name, series))
|
||||
continue;
|
||||
}
|
||||
for (let arch in split(as_string(arch_candidates), " ")) {
|
||||
if (arch == "")
|
||||
continue;
|
||||
for (let asset in array_or_empty(release.assets)) {
|
||||
if (type(asset) != "object")
|
||||
continue;
|
||||
let name = as_string(asset.name || "");
|
||||
let url = as_string(asset.browser_download_url || "");
|
||||
if (url != "" && release_asset_matches_arch(name, "olcrtc", arch, asset_ext)) {
|
||||
print(arch, "\t", name, "\t", url, "\t", as_string(release.html_url || ""), "\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sing_box_extended_release_tag() {
|
||||
for (let release in releases_array_or_wrapped(read_stdin_json())) {
|
||||
if (type(release) != "object")
|
||||
@@ -1444,6 +1516,10 @@ else if (mode == "release-select-arch-suffix-asset")
|
||||
release_select_arch_suffix_asset(ARGV[1], ARGV[2]);
|
||||
else if (mode == "byedpi-select-asset")
|
||||
byedpi_select_asset(ARGV[1], ARGV[2], ARGV[3]);
|
||||
else if (mode == "wdtt-select-asset")
|
||||
wdtt_select_asset(ARGV[1], ARGV[2], ARGV[3]);
|
||||
else if (mode == "olcrtc-select-asset")
|
||||
olcrtc_select_asset(ARGV[1], ARGV[2], ARGV[3]);
|
||||
else if (mode == "sing-box-extended-release-tag")
|
||||
sing_box_extended_release_tag();
|
||||
else if (mode == "sing-box-lx-release-tag")
|
||||
|
||||
@@ -631,7 +631,7 @@ function normalize_component_name(component) {
|
||||
function valid_component_name(component) {
|
||||
component = normalize_component_name(component);
|
||||
return component == "tachyon" || component == "sing_box" || component == "zapret" ||
|
||||
component == "zapret2" || component == "byedpi" || component == "tailscale";
|
||||
component == "zapret2" || component == "byedpi" || component == "wdtt" || component == "olcrtc" || component == "tailscale";
|
||||
}
|
||||
|
||||
function settings_component_auto_update_enabled(settings) {
|
||||
@@ -1604,7 +1604,7 @@ function component_update_check_cache() {
|
||||
let results = [];
|
||||
|
||||
if (enabled) {
|
||||
for (let component in [ "tachyon", "sing_box", "zapret", "zapret2", "byedpi" ]) {
|
||||
for (let component in [ "tachyon", "sing_box", "zapret", "zapret2", "byedpi", "wdtt", "olcrtc" ]) {
|
||||
let value = read_json_file(component_update_check_cache_path(component));
|
||||
if (component_update_check_result_cacheable(value))
|
||||
push(results, value);
|
||||
@@ -2030,6 +2030,10 @@ function automatic_component_check_names() {
|
||||
push(result, "zapret2");
|
||||
if (module_success([ LIB_DIR + "/providers/byedpi/runtime.uc", "installed" ]))
|
||||
push(result, "byedpi");
|
||||
if (module_success([ LIB_DIR + "/providers/wdtt/runtime.uc", "installed" ]))
|
||||
push(result, "wdtt");
|
||||
if (module_success([ LIB_DIR + "/providers/olcrtc/runtime.uc", "installed" ]))
|
||||
push(result, "olcrtc");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -306,6 +306,34 @@ function is_connections_action(action) {
|
||||
return action == "connection" || is_legacy_connection_action(action);
|
||||
}
|
||||
|
||||
function is_wdtt_action(action) {
|
||||
return as_string(action) == "wdtt";
|
||||
}
|
||||
|
||||
function is_olcrtc_action(action) {
|
||||
return as_string(action) == "olcrtc";
|
||||
}
|
||||
|
||||
function wdtt_sections() {
|
||||
let result = [];
|
||||
let sections = uci_core.section_objects(CONFIG_NAME, "section");
|
||||
for (let section in sections) {
|
||||
if (bool_option(section, "enabled", true) && option(section, "action", "") == "wdtt")
|
||||
push(result, section);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function olcrtc_sections() {
|
||||
let result = [];
|
||||
let sections = uci_core.section_objects(CONFIG_NAME, "section");
|
||||
for (let section in sections) {
|
||||
if (bool_option(section, "enabled", true) && option(section, "action", "") == "olcrtc")
|
||||
push(result, section);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function normalize_action(action) {
|
||||
action = as_string(action);
|
||||
return is_connections_action(action) ? "connection" : action;
|
||||
@@ -617,6 +645,26 @@ function dscp_value(section) {
|
||||
return list_option_value_from_array(dscp_list(section));
|
||||
}
|
||||
|
||||
function routed_dns_enabled(section) {
|
||||
return bool_option(section, "routed_dns_enabled", false);
|
||||
}
|
||||
|
||||
function routed_dns_type(section) {
|
||||
let t = option(section, "routed_dns_type", "udp");
|
||||
if (!contains(["udp", "dot", "doh", "doq"], t))
|
||||
t = "udp";
|
||||
return t;
|
||||
}
|
||||
|
||||
function routed_dns_servers(section) {
|
||||
let servers = list_option(section, "routed_dns_server");
|
||||
if (length(servers) == 0) {
|
||||
let single = option(section, "routed_dns_server", "");
|
||||
servers = [single];
|
||||
}
|
||||
return servers;
|
||||
}
|
||||
|
||||
function contains(arr, val) {
|
||||
if (type(arr) != "array") return false;
|
||||
for (let item in arr) {
|
||||
@@ -1552,6 +1600,13 @@ return {
|
||||
subscription_download_targets,
|
||||
subscription_download_target_port,
|
||||
has_dns_matchers,
|
||||
routed_dns_enabled,
|
||||
routed_dns_type,
|
||||
routed_dns_servers,
|
||||
item_index_from_cursor,
|
||||
cli_delete_section
|
||||
cli_delete_section,
|
||||
wdtt_sections,
|
||||
olcrtc_sections,
|
||||
is_wdtt_action,
|
||||
is_olcrtc_action
|
||||
};
|
||||
|
||||
@@ -8,6 +8,8 @@ let subscription_parser_module = null;
|
||||
let zapret_validator_module = null;
|
||||
let zapret2_validator_module = null;
|
||||
let byedpi_validator_module = null;
|
||||
let wdtt_validator_module = null;
|
||||
let olcrtc_validator_module = null;
|
||||
let constants_module = null;
|
||||
let core_url = require("core.url");
|
||||
let core_ip = require("core.ip");
|
||||
@@ -695,6 +697,18 @@ function byedpi_validator() {
|
||||
return byedpi_validator_module;
|
||||
}
|
||||
|
||||
function wdtt_validator() {
|
||||
if (wdtt_validator_module == null)
|
||||
wdtt_validator_module = require("providers.wdtt.validator");
|
||||
return wdtt_validator_module;
|
||||
}
|
||||
|
||||
function olcrtc_validator() {
|
||||
if (olcrtc_validator_module == null)
|
||||
olcrtc_validator_module = require("providers.olcrtc.validator");
|
||||
return olcrtc_validator_module;
|
||||
}
|
||||
|
||||
function runtime_constants() {
|
||||
if (constants_module == null)
|
||||
constants_module = require("core.constants");
|
||||
@@ -769,13 +783,14 @@ function rule_action(section) {
|
||||
function rule_action_supported(action) {
|
||||
return contains([ "connection", "proxy", "outbound", "vpn", "awg", "warp",
|
||||
"anytls", "snell", "mieru", "sudoku", "masque", "openvpn",
|
||||
"bypass", "block", "dns", "zapret", "zapret2", "byedpi", "hosts" ], as_string(action));
|
||||
"bypass", "block", "dns", "zapret", "zapret2", "byedpi", "hosts",
|
||||
"wdtt", "olcrtc" ], as_string(action));
|
||||
}
|
||||
|
||||
function server_routing_section_action_supported(action) {
|
||||
return contains([ "connection", "proxy", "outbound", "vpn", "awg", "warp",
|
||||
"anytls", "snell", "mieru", "sudoku", "masque", "openvpn",
|
||||
"zapret", "zapret2", "byedpi" ], as_string(action));
|
||||
"zapret", "zapret2", "byedpi", "wdtt", "olcrtc" ], as_string(action));
|
||||
}
|
||||
|
||||
function duration_to_seconds_value(value) {
|
||||
@@ -1367,6 +1382,53 @@ function validate_provider_strategy(kind, section, context) {
|
||||
fail_validation("Invalid ByeDPI strategy for rule '" + name + "': " + result.message);
|
||||
}
|
||||
|
||||
function validate_wdtt_section(section, context) {
|
||||
let name = section_name(section);
|
||||
let uri = option(section, "warp_flow_uri", "");
|
||||
|
||||
if (uri == "") {
|
||||
fail_validation("WDTT rule '" + name + "' has empty URI. Aborted.");
|
||||
return;
|
||||
}
|
||||
|
||||
let result = wdtt_validator().parse_wdtt_uri(uri);
|
||||
if (!result.valid)
|
||||
fail_validation("Invalid WDTT URI for rule '" + name + "': " + result.message + ". Aborted.");
|
||||
|
||||
let mode = option(section, "warp_flow_mode", "");
|
||||
if (mode != "" && !contains(["freedom", "captive", "camou"], mode))
|
||||
fail_validation("Invalid WDTT mode '" + mode + "' in rule '" + name + "'. Aborted.");
|
||||
|
||||
let device_id = option(section, "warp_flow_device_id", "");
|
||||
if (device_id != "" && !wdtt_validator().validate_device_id(device_id))
|
||||
fail_validation("Invalid WDTT device ID '" + device_id + "' in rule '" + name + "'. Aborted.");
|
||||
}
|
||||
|
||||
function validate_olcrtc_section(section, context) {
|
||||
let name = section_name(section);
|
||||
let provider = option(section, "olcrtc_provider", "");
|
||||
|
||||
if (provider == "") {
|
||||
fail_validation("OlcRTC rule '" + name + "' has empty provider. Aborted.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!contains(["livekit", "wrt"], provider))
|
||||
fail_validation("Invalid OlcRTC provider '" + provider + "' in rule '" + name + "'. Aborted.");
|
||||
|
||||
let transport = option(section, "olcrtc_transport", "whep");
|
||||
if (!contains(["whep", "whip"], transport))
|
||||
fail_validation("Invalid OlcRTC transport '" + transport + "' in rule '" + name + "'. Aborted.");
|
||||
|
||||
let room_id = option(section, "olcrtc_room_id", "");
|
||||
if (room_id == "")
|
||||
fail_validation("OlcRTC rule '" + name + "' has empty room ID. Aborted.");
|
||||
|
||||
let crypto_key = option(section, "olcrtc_crypto_key", "");
|
||||
if (crypto_key == "")
|
||||
fail_validation("OlcRTC rule '" + name + "' has empty crypto key. Aborted.");
|
||||
}
|
||||
|
||||
function dns_action_has_domain_matchers(section) {
|
||||
for (let key in [ "domain", "domain_suffix", "domain_keyword", "domain_regex" ])
|
||||
if (option(section, key, "") != "" || option(section, key + "_text", "") != "" || length(list_option(section, key)) > 0)
|
||||
@@ -1467,6 +1529,22 @@ function validate_rule(section, sections, context) {
|
||||
validate_provider_strategy("byedpi", section, context);
|
||||
}
|
||||
|
||||
if (action == "wdtt") {
|
||||
if (!context.wdtt_installed) {
|
||||
validate_common_rule_references(section, context);
|
||||
return;
|
||||
}
|
||||
validate_wdtt_section(section, context);
|
||||
}
|
||||
|
||||
if (action == "olcrtc") {
|
||||
if (!context.olcrtc_installed) {
|
||||
validate_common_rule_references(section, context);
|
||||
return;
|
||||
}
|
||||
validate_olcrtc_section(section, context);
|
||||
}
|
||||
|
||||
if (connections.is_connections_action(action)) {
|
||||
validate_dashboard_filter(section);
|
||||
|
||||
@@ -1536,6 +1614,23 @@ function validate_rule(section, sections, context) {
|
||||
for (let value in list_option(section, "domain_suffix"))
|
||||
validate_combined_domain_value(value, name);
|
||||
validate_combined_domain_text_value(option(section, "domain_suffix_text", ""), name);
|
||||
|
||||
if (connections.routed_dns_enabled(section)) {
|
||||
if (action == "dns" || action == "hosts" || action == "bypass" || action == "block")
|
||||
fail_validation("Rule '" + name + "' has routed DNS enabled but action '" + action + "' does not support routed DNS. Use a proxy action (connection, zapret, etc.). Aborted.");
|
||||
let routed_type = connections.routed_dns_type(section);
|
||||
if (!contains(["udp", "dot", "doh", "doq"], routed_type))
|
||||
fail_validation("Rule '" + name + "' has invalid routed DNS type '" + routed_type + "'. Use udp, dot, doh, or doq. Aborted.");
|
||||
let routed_servers = connections.routed_dns_servers(section);
|
||||
if (length(routed_servers) == 0)
|
||||
fail_validation("Rule '" + name + "' has routed DNS enabled but no DNS server specified. Aborted.");
|
||||
for (let srv in routed_servers)
|
||||
if (!dns_server_value_valid(srv))
|
||||
fail_validation("Rule '" + name + "' has an invalid routed DNS server '" + srv + "'. Aborted.");
|
||||
if (!connections.has_dns_matchers(section) && length(list_option(section, "fully_routed_ips")) == 0)
|
||||
fail_validation("Rule '" + name + "' has routed DNS enabled but no domain conditions or forced devices. Routed DNS requires at least one domain matcher. Aborted.");
|
||||
}
|
||||
|
||||
validate_common_rule_references(section, context);
|
||||
}
|
||||
|
||||
@@ -1992,6 +2087,8 @@ function context_from_runtime() {
|
||||
byedpi_installed: file_executable(constant_value(constants, "BYEDPI_BIN")),
|
||||
zapret_installed: file_executable(constant_value(constants, "ZAPRET_PROVIDER_NFQWS_BIN")),
|
||||
zapret2_installed: file_executable(constant_value(constants, "ZAPRET2_PROVIDER_NFQWS2_BIN")),
|
||||
wdtt_installed: file_executable(constant_value(constants, "WDTT_BIN")),
|
||||
olcrtc_installed: file_executable(constant_value(constants, "OLCRTC_BIN")),
|
||||
zapret_provider_nfqws_bin: constant_value(constants, "ZAPRET_PROVIDER_NFQWS_BIN"),
|
||||
zapret2_provider_nfqws2_bin: constant_value(constants, "ZAPRET2_PROVIDER_NFQWS2_BIN"),
|
||||
zapret_route_mark_base: constant_value(constants, "ZAPRET_ROUTE_MARK_BASE"),
|
||||
|
||||
@@ -225,6 +225,25 @@ function constants_map() {
|
||||
c.BYEDPI_OPEN_FILES_LIMIT = env("BYEDPI_OPEN_FILES_LIMIT", "4096");
|
||||
c.BYEDPI_DEFAULT_CMD_OPTS = env("BYEDPI_DEFAULT_CMD_OPTS", "-o 2 --auto=t,r,a,s -d 2");
|
||||
|
||||
c.WDTT_BIN = env("WDTT_BIN", "/usr/bin/wdtt");
|
||||
c.WDTT_SERVICE_INIT = env("WDTT_SERVICE_INIT", "/etc/init.d/wdtt");
|
||||
c.WDTT_STATE_DIR = env("WDTT_STATE_DIR", "/var/run/tachyon/wdtt");
|
||||
c.WDTT_PID_DIR = env("WDTT_PID_DIR", c.WDTT_STATE_DIR + "/pid");
|
||||
c.WDTT_LOG_DIR = env("WDTT_LOG_DIR", c.WDTT_STATE_DIR + "/log");
|
||||
c.WDTT_LISTEN_ADDRESS = env("WDTT_LISTEN_ADDRESS", "127.0.0.1");
|
||||
c.WDTT_PORT_BASE = env("WDTT_PORT_BASE", "1081");
|
||||
c.WDTT_RESPAWN_DELAY = env("WDTT_RESPAWN_DELAY", "5");
|
||||
c.WDTT_DEFAULT_MODE = env("WDTT_DEFAULT_MODE", "freedom");
|
||||
|
||||
c.OLCRTC_BIN = env("OLCRTC_BIN", "/usr/bin/olcrtc");
|
||||
c.OLCRTC_SERVICE_INIT = env("OLCRTC_SERVICE_INIT", "/etc/init.d/olcrtc");
|
||||
c.OLCRTC_STATE_DIR = env("OLCRTC_STATE_DIR", "/var/run/tachyon/olcrtc");
|
||||
c.OLCRTC_PID_DIR = env("OLCRTC_PID_DIR", c.OLCRTC_STATE_DIR + "/pid");
|
||||
c.OLCRTC_LOG_DIR = env("OLCRTC_LOG_DIR", c.OLCRTC_STATE_DIR + "/log");
|
||||
c.OLCRTC_LISTEN_ADDRESS = env("OLCRTC_LISTEN_ADDRESS", "127.0.0.1");
|
||||
c.OLCRTC_PORT_BASE = env("OLCRTC_PORT_BASE", "1082");
|
||||
c.OLCRTC_RESPAWN_DELAY = env("OLCRTC_RESPAWN_DELAY", "5");
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,8 @@ const SINGBOX_RUNTIME_UC = LIB_DIR + "/singbox/runtime.uc";
|
||||
const ZAPRET_RUNTIME_UC = LIB_DIR + "/providers/zapret/runtime.uc";
|
||||
const ZAPRET2_RUNTIME_UC = LIB_DIR + "/providers/zapret2/runtime.uc";
|
||||
const BYEDPI_RUNTIME_UC = LIB_DIR + "/providers/byedpi/runtime.uc";
|
||||
const WDTT_RUNTIME_UC = LIB_DIR + "/providers/wdtt/runtime.uc";
|
||||
const OLCRTC_RUNTIME_UC = LIB_DIR + "/providers/olcrtc/runtime.uc";
|
||||
const TAILSCALE_RUNTIME_UC = LIB_DIR + "/providers/tailscale/runtime.uc";
|
||||
const ZAPRET_VALIDATOR_UC = LIB_DIR + "/providers/zapret/validator.uc";
|
||||
const ZAPRET2_VALIDATOR_UC = LIB_DIR + "/providers/zapret2/validator.uc";
|
||||
@@ -2658,6 +2660,37 @@ function run_doctor_checks_impl(repair) {
|
||||
}
|
||||
}
|
||||
|
||||
// 4b2. Dnsmasq dns_redirect — when dhcp.@dnsmasq[0].dns_redirect='1',
|
||||
// firewall4 creates DNAT rules that redirect external DNS queries to the
|
||||
// router's :53. If Tachyon TProxy has already marked those packets, the
|
||||
// DNAT rewrites destination to 192.168.1.1:53 and sing-box hijack-dns
|
||||
// creates transparent sockets that collide with dnsmasq.
|
||||
if (!agh_primary) {
|
||||
let dns_redirect = uci_core.get("dhcp.@dnsmasq[0].dns_redirect");
|
||||
if (dns_redirect != "1") {
|
||||
doc_check("✅", "dnsmasq dns_redirect", "disabled (OK)", "");
|
||||
} else {
|
||||
issues++;
|
||||
if (!DOCTOR_REPAIR_MODE) {
|
||||
doc_plan("uci set dhcp.@dnsmasq[0].dns_redirect='0' + dnsmasq restart");
|
||||
doc_check("⚠️", "dnsmasq dns_redirect", "enabled (causes DNAT → port 53 collision with sing-box)",
|
||||
"→ WILL FIX (doctor --fix): отключение dns_redirect");
|
||||
} else {
|
||||
doc_set("dhcp.@dnsmasq[0].dns_redirect", "0");
|
||||
doc_commit("dhcp");
|
||||
command_status("/etc/init.d/dnsmasq restart >/dev/null 2>&1");
|
||||
command_status("sleep 1");
|
||||
let dr2 = uci_core.get("dhcp.@dnsmasq[0].dns_redirect");
|
||||
if (dr2 != "1") {
|
||||
doc_check("❌", "dnsmasq dns_redirect", "enabled", "→ FIXED: dns_redirect=0");
|
||||
fixed++;
|
||||
} else {
|
||||
doc_check("❌", "dnsmasq dns_redirect", "enabled", "→ не удалось отключить dns_redirect");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4c. Resolv.conf symlink
|
||||
let resolv_link = "";
|
||||
// Throws when /etc/resolv.conf is a regular file rather than a symlink,
|
||||
@@ -3354,6 +3387,61 @@ function run_doctor_checks_impl(repair) {
|
||||
}
|
||||
}
|
||||
|
||||
// 20b. Port 53 Socket Collision — sing-box hijack-dns on tproxy-in creates
|
||||
// transparent write-only sockets that collide with dnsmasq on :53 via
|
||||
// SO_REUSEADDR. Diagnostic: find sing-box UNCONN sockets on :53 with
|
||||
// non-zero Recv-Q (parasitic write-back sockets that steal UDP packets
|
||||
// from dnsmasq).
|
||||
{
|
||||
let ss_output = command_capture("ss -u -a -e -n 'sport = :53' 2>/dev/null").output;
|
||||
let parasitic_count = 0;
|
||||
let collision_detail = "";
|
||||
for (let line in split(ss_output, "\n")) {
|
||||
if (index(line, "UNCONN") < 0)
|
||||
continue;
|
||||
if (index(line, "sing-box") < 0 && index(line, "sing_box") < 0)
|
||||
continue;
|
||||
let fields = split(trim(line), /[ \t]+/);
|
||||
if (length(fields) < 5)
|
||||
continue;
|
||||
let recv_q = int(fields[1]);
|
||||
if (recv_q > 0) {
|
||||
parasitic_count++;
|
||||
if (collision_detail == "")
|
||||
collision_detail = fields[3] + " Recv-Q=" + as_string(recv_q);
|
||||
}
|
||||
}
|
||||
if (parasitic_count == 0) {
|
||||
doc_check("✅", "Port 53 socket collision", "none", "");
|
||||
} else {
|
||||
issues++;
|
||||
if (!DOCTOR_REPAIR_MODE) {
|
||||
doc_plan("restart sing-box to clear parasitic :53 sockets");
|
||||
doc_check("⚠️", "Port 53 socket collision", as_string(parasitic_count) + " sing-box sockets on :53 (" + collision_detail + ")",
|
||||
"→ sing-box hijack-dns создаёт прозрачные write-only сокеты, перехватывающие UDP у dnsmasq; WILL FIX: restart sing-box");
|
||||
} else {
|
||||
command_status("/etc/init.d/sing-box restart >/dev/null 2>&1");
|
||||
command_status("sleep 2");
|
||||
let ss_check = command_capture("ss -u -a -e -n 'sport = :53' 2>/dev/null").output;
|
||||
let still_parasitic = 0;
|
||||
for (let line2 in split(ss_check, "\n")) {
|
||||
if (index(line2, "UNCONN") < 0) continue;
|
||||
if (index(line2, "sing-box") < 0 && index(line2, "sing_box") < 0) continue;
|
||||
let f2 = split(trim(line2), /[ \t]+/);
|
||||
if (length(f2) >= 5 && int(f2[1]) > 0) still_parasitic++;
|
||||
}
|
||||
if (still_parasitic == 0) {
|
||||
doc_check("❌", "Port 53 socket collision", as_string(parasitic_count) + " sing-box sockets on :53",
|
||||
"→ FIXED: sing-box перезапущен, паразитные сокеты очищены");
|
||||
fixed++;
|
||||
} else {
|
||||
doc_check("❌", "Port 53 socket collision", "persists after restart",
|
||||
"→ не удалось устранить — проверьте конфигурацию hijack-dns inbound фильтра");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 21. Subscription Cache Age — sing-box can route through days-old node
|
||||
// lists while the subscription URL itself is reachable.
|
||||
if (has_sections && cfg.subscription_url) {
|
||||
@@ -5020,6 +5108,10 @@ else if (mode == "get-zapret2-status")
|
||||
exit(module_passthrough(ZAPRET2_RUNTIME_UC, [ "status" ]));
|
||||
else if (mode == "get-byedpi-status")
|
||||
exit(module_passthrough(BYEDPI_RUNTIME_UC, [ "status" ]));
|
||||
else if (mode == "get-wdtt-status")
|
||||
exit(module_passthrough(WDTT_RUNTIME_UC, [ "status" ]));
|
||||
else if (mode == "get-olcrtc-status")
|
||||
exit(module_passthrough(OLCRTC_RUNTIME_UC, [ "status" ]));
|
||||
else if (mode == "get-tailscale-status")
|
||||
exit(module_passthrough(TAILSCALE_RUNTIME_UC, [ "status" ]));
|
||||
else if (mode == "get-tailscale-peers")
|
||||
|
||||
@@ -584,7 +584,8 @@ function section_action(section) {
|
||||
|
||||
function action_captures_traffic(action) {
|
||||
return action == "connection" || action == "proxy" || action == "outbound" || action == "vpn" ||
|
||||
action == "awg" || action == "warp" || action == "block" || action == "zapret" || action == "zapret2" || action == "byedpi";
|
||||
action == "awg" || action == "warp" || action == "block" || action == "zapret" || action == "zapret2" ||
|
||||
action == "byedpi" || action == "wdtt" || action == "olcrtc";
|
||||
}
|
||||
|
||||
function section_priority_action(section) {
|
||||
@@ -698,9 +699,18 @@ function nft_excluded_source_match_args(section, family) {
|
||||
return [];
|
||||
let ip_key = family == 6 ? "ip6" : "ip";
|
||||
let addrs = [];
|
||||
for (let ip in excluded)
|
||||
if (core_ip.ip_family(as_string(ip)) == family)
|
||||
push(addrs, as_string(ip));
|
||||
for (let item in excluded) {
|
||||
let val = trim(as_string(item));
|
||||
if (val == "") continue;
|
||||
let is_mac = match(val, /^([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$/) != null;
|
||||
if (is_mac) {
|
||||
for (let res_ip in core_ip.resolve_mac_to_ips(val))
|
||||
if (core_ip.ip_family(res_ip) == family)
|
||||
push(addrs, res_ip);
|
||||
} else if (core_ip.ip_family(val) == family) {
|
||||
push(addrs, val);
|
||||
}
|
||||
}
|
||||
if (length(addrs) == 0)
|
||||
return [];
|
||||
return [ ip_key, "saddr", "!=", "{ " + join(", ", addrs) + " }" ];
|
||||
@@ -2369,6 +2379,9 @@ function nft_rule_signature_body(body, section) {
|
||||
body = signature_add_value(body, "rule." + section_name + ".fully_routed_ips", option(section, "fully_routed_ips", ""));
|
||||
body = signature_add_value(body, "rule." + section_name + ".excluded_ips", option(section, "excluded_ips", ""));
|
||||
body = signature_add_value(body, "rule." + section_name + ".excluded_protocol", option(section, "excluded_protocol", ""));
|
||||
body = signature_add_value(body, "rule." + section_name + ".routed_dns_enabled", bool_option(section, "routed_dns_enabled", false) ? "1" : "0");
|
||||
body = signature_add_value(body, "rule." + section_name + ".routed_dns_type", option(section, "routed_dns_type", ""));
|
||||
body = signature_add_value(body, "rule." + section_name + ".routed_dns_server", option(section, "routed_dns_server", ""));
|
||||
body = signature_add_value(body, "rule." + section_name + ".protocol", option(section, "protocol", ""));
|
||||
let comm_subnets = bool_option(section, "community_subnets", true) ? filter_community_subnet_lists_value(connections.community_lists_value(section)) : "";
|
||||
body = signature_add_value(body, "rule." + section_name + ".community_subnet_lists", comm_subnets);
|
||||
@@ -2771,6 +2784,15 @@ function source_aware_dns_values(sections, deferred_sections) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (connections.routed_dns_enabled(section)) {
|
||||
for (let value in nft_csv_values(section_source_ip_values(section))) {
|
||||
if (!seen[value]) {
|
||||
seen[value] = true;
|
||||
push(values, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let settings = uci_settings();
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
let constants = require("core.constants");
|
||||
|
||||
function config(ctx) {
|
||||
let env_get = function(key, fallback) {
|
||||
let val = getenv(key);
|
||||
return val != null ? val : fallback;
|
||||
};
|
||||
|
||||
return {
|
||||
kind: "olcrtc",
|
||||
action: "olcrtc",
|
||||
config_path: env_get("TACHYON_OLCRTC_CONFIG", "/etc/olcrtc/client.yaml"),
|
||||
service_init: env_get("TACHYON_OLCRTC_SERVICE_INIT", "/etc/init.d/olcrtc"),
|
||||
binary: env_get("TACHYON_OLCRTC_BIN", "/usr/bin/olcrtc"),
|
||||
default_socks_host: "127.0.0.1",
|
||||
default_socks_port: "1080",
|
||||
default_dns: "8.8.8.8:53",
|
||||
default_provider: "jitsi",
|
||||
default_transport: "datachannel",
|
||||
package_name: "olcrtc",
|
||||
runtime_path: ctx.lib_dir + "/providers/olcrtc/runtime.uc",
|
||||
config_name: "olcrtc",
|
||||
status_label: "olcrtc",
|
||||
check_prefix: "olcrtc"
|
||||
};
|
||||
}
|
||||
|
||||
function validator() {
|
||||
return require("providers.olcrtc.validator");
|
||||
}
|
||||
|
||||
return { config, validator };
|
||||
@@ -0,0 +1,425 @@
|
||||
#!/usr/bin/env ucode
|
||||
|
||||
let fs = require("fs");
|
||||
let common = require("core.common");
|
||||
let uci_core = require("core.uci");
|
||||
let connections = require("config.connections");
|
||||
|
||||
let as_string = common.as_string;
|
||||
let bool_value = common.bool_value;
|
||||
let object_or_empty = common.object_or_empty;
|
||||
|
||||
const MODE = as_string(getenv("TACHYON_CONFIG_NAME")) || "tachyon";
|
||||
const LIB_DIR = as_string(getenv("TACHYON_LIB_DIR")) || "/usr/lib/tachyon";
|
||||
const CONFIG_NAME = MODE;
|
||||
|
||||
let cfg = require("providers.olcrtc.common").config({ lib_dir: LIB_DIR });
|
||||
let olcrtc_validator = require("providers.olcrtc.validator");
|
||||
|
||||
function log_message(msg, level) {
|
||||
level = level || "info";
|
||||
printf("%s\n", msg);
|
||||
system("logger -t " + MODE + " -p " + (level == "fatal" ? "user.err" : level == "warn" ? "user.warning" : "user.info") + " '" + msg + "'");
|
||||
}
|
||||
|
||||
function command_from_args(args) {
|
||||
let parts = [];
|
||||
for (let arg in args)
|
||||
push(parts, common.shell_quote(as_string(arg)));
|
||||
return join(" ", parts);
|
||||
}
|
||||
|
||||
function command_output(cmd) {
|
||||
let output = trim(popen(cmd, "r") || "");
|
||||
return output;
|
||||
}
|
||||
|
||||
function command_output_from_args(args) {
|
||||
return command_output(command_from_args(args));
|
||||
}
|
||||
|
||||
function command_success_from_args(args) {
|
||||
return system(command_from_args(args)) == 0;
|
||||
}
|
||||
|
||||
function option(section, key, fallback) {
|
||||
let val = section[key];
|
||||
if (val == null)
|
||||
return fallback || "";
|
||||
if (type(val) == "array")
|
||||
return join(" ", val);
|
||||
return as_string(val);
|
||||
}
|
||||
|
||||
function bool_option(section, key, fallback) {
|
||||
let val = section[key];
|
||||
if (val == null)
|
||||
return fallback || false;
|
||||
return bool_value(val);
|
||||
}
|
||||
|
||||
function list_option(section, key) {
|
||||
let val = section[key];
|
||||
if (val == null)
|
||||
return [];
|
||||
if (type(val) == "array")
|
||||
return val;
|
||||
return split(trim(as_string(val)), " \t\n\r");
|
||||
}
|
||||
|
||||
function section_name(section) {
|
||||
return as_string(section[".name"] || "");
|
||||
}
|
||||
|
||||
function read_json_file(path) {
|
||||
let data = fs.readfile(path);
|
||||
if (data == null || data == "")
|
||||
return null;
|
||||
return json(data);
|
||||
}
|
||||
|
||||
function yaml_quote(value) {
|
||||
value = as_string(value);
|
||||
if (value == "")
|
||||
return "''";
|
||||
if (match(value, /^[a-zA-Z0-9_./:-]+$/) != null)
|
||||
return value;
|
||||
return "'" + replace(value, "'", "''") + "'";
|
||||
}
|
||||
|
||||
function enabled_sections() {
|
||||
return connections.olcrtc_sections();
|
||||
}
|
||||
|
||||
function enabled_rule_count() {
|
||||
let sections = enabled_sections();
|
||||
return length(sections);
|
||||
}
|
||||
|
||||
function provider_available() {
|
||||
return fs.stat(cfg.binary) != null;
|
||||
}
|
||||
|
||||
function service_init_exists() {
|
||||
return fs.stat(cfg.service_init) != null;
|
||||
}
|
||||
|
||||
function service_running() {
|
||||
return command_success_from_args([cfg.service_init, "status"]);
|
||||
}
|
||||
|
||||
function service_enabled() {
|
||||
return command_success_from_args([cfg.service_init, "enabled"]);
|
||||
}
|
||||
|
||||
function package_version() {
|
||||
let ver = trim(fs.readfile("/etc/olcrtc/.version") || "");
|
||||
if (ver != "")
|
||||
return ver;
|
||||
return "";
|
||||
}
|
||||
|
||||
function fetch_url(url) {
|
||||
let tmp = "/tmp/tachyon-olcrtc-sub";
|
||||
if (command_success_from_args(["curl", "-fsSL", "-o", tmp, "--connect-timeout", "20", "--max-time", "20", url]))
|
||||
return trim(fs.readfile(tmp) || "");
|
||||
if (command_success_from_args(["wget", "-q", "-O", tmp, "--timeout=20", url]))
|
||||
return trim(fs.readfile(tmp) || "");
|
||||
if (command_success_from_args(["uclient-fetch", "-q", "-O", tmp, "--timeout=20", url]))
|
||||
return trim(fs.readfile(tmp) || "");
|
||||
return "";
|
||||
}
|
||||
|
||||
function first_uri_from_subscription(data) {
|
||||
let lines = split(data, "\n\r");
|
||||
for (let line in lines) {
|
||||
line = trim(line);
|
||||
if (substr(line, 0, 9) == "olcrtc://")
|
||||
return line;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function resolve_connection(section) {
|
||||
let links = list_option(section, "subscription_links");
|
||||
let resolved = null;
|
||||
let used_link = "";
|
||||
let subscription_name = "";
|
||||
let subscription_refresh = "";
|
||||
|
||||
for (let link in links) {
|
||||
link = trim(as_string(link));
|
||||
if (link == "")
|
||||
continue;
|
||||
|
||||
if (substr(link, 0, 9) == "olcrtc://") {
|
||||
let parsed = olcrtc_validator.parse_uri(link);
|
||||
if (parsed.valid) {
|
||||
resolved = parsed;
|
||||
used_link = link;
|
||||
break;
|
||||
}
|
||||
log_message("Invalid olcrtc:// URI: " + parsed.reason, "warn");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (substr(link, 0, 8) == "https://" || substr(link, 0, 7) == "http://") {
|
||||
let data = fetch_url(link);
|
||||
if (data == "") {
|
||||
log_message("Failed to fetch subscription: " + link, "warn");
|
||||
continue;
|
||||
}
|
||||
let uri = first_uri_from_subscription(data);
|
||||
if (uri == "") {
|
||||
log_message("No olcrtc:// URI found in subscription: " + link, "warn");
|
||||
continue;
|
||||
}
|
||||
let parsed = olcrtc_validator.parse_uri(uri);
|
||||
if (parsed.valid) {
|
||||
resolved = parsed;
|
||||
used_link = link;
|
||||
let lines = split(data, "\n\r");
|
||||
for (let line in lines) {
|
||||
line = trim(line);
|
||||
if (substr(line, 0, 6) == "#name:")
|
||||
subscription_name = trim(substr(line, 6));
|
||||
else if (substr(line, 0, 8) == "#refresh:")
|
||||
subscription_refresh = trim(substr(line, 8));
|
||||
}
|
||||
break;
|
||||
}
|
||||
log_message("Invalid olcrtc URI in subscription: " + parsed.reason, "warn");
|
||||
}
|
||||
}
|
||||
|
||||
if (resolved == null)
|
||||
return { valid: false, reason: "No valid connection found in subscription links" };
|
||||
|
||||
let provider = option(section, "provider", "");
|
||||
if (provider == "")
|
||||
provider = resolved.provider;
|
||||
let transport = option(section, "transport", "");
|
||||
if (transport == "")
|
||||
transport = resolved.transport;
|
||||
let room_id = option(section, "room_id", "");
|
||||
if (room_id == "")
|
||||
room_id = resolved.room_id;
|
||||
let crypto_key = option(section, "crypto_key", "");
|
||||
if (crypto_key == "")
|
||||
crypto_key = resolved.crypto_key;
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
provider: provider,
|
||||
transport: transport,
|
||||
room_id: room_id,
|
||||
crypto_key: crypto_key,
|
||||
mimo: resolved.mimo,
|
||||
payload: resolved.payload,
|
||||
used_link: used_link,
|
||||
subscription_name: subscription_name,
|
||||
subscription_refresh: subscription_refresh
|
||||
};
|
||||
}
|
||||
|
||||
function payload_option_mapping() {
|
||||
return {
|
||||
"vp8-fps": "vp8_fps",
|
||||
"vp8-batch": "vp8_batch",
|
||||
"fps": "sei_fps",
|
||||
"batch": "sei_batch",
|
||||
"video-w": "video_w",
|
||||
"video-h": "video_h",
|
||||
"video-fps": "video_fps",
|
||||
"video-codec": "video_codec",
|
||||
"video-qr-size": "video_qr_size",
|
||||
"video-qr-recovery": "video_qr_recovery",
|
||||
"video-tile-module": "video_tile_module",
|
||||
"video-tile-rs": "video_tile_rs"
|
||||
};
|
||||
}
|
||||
|
||||
function payload_uci_commands(payload) {
|
||||
let cmds = [];
|
||||
if (payload == "" || payload == null)
|
||||
return cmds;
|
||||
let mapping = payload_option_mapping();
|
||||
let pairs = split(payload, "&");
|
||||
for (let pair in pairs) {
|
||||
let kv = split(pair, "=", 2);
|
||||
if (length(kv) == 2) {
|
||||
let key = kv[0];
|
||||
let val = kv[1];
|
||||
if (mapping[key] != null)
|
||||
push(cmds, "uci set olcrtc.config." + mapping[key] + "=" + yaml_quote(val));
|
||||
}
|
||||
}
|
||||
return cmds;
|
||||
}
|
||||
|
||||
function write_olcrtc_config(section, connection) {
|
||||
let uci_cmds = [
|
||||
"uci set olcrtc.config.carrier=" + yaml_quote(connection.provider),
|
||||
"uci set olcrtc.config.transport=" + yaml_quote(connection.transport),
|
||||
"uci set olcrtc.config.room_id=" + yaml_quote(connection.room_id),
|
||||
"uci set olcrtc.config.key=" + yaml_quote(connection.crypto_key),
|
||||
"uci set olcrtc.config.socks_host=" + yaml_quote(option(section, "socks_host", cfg.default_socks_host)),
|
||||
"uci set olcrtc.config.socks_port=" + yaml_quote(option(section, "socks_port", cfg.default_socks_port)),
|
||||
"uci set olcrtc.config.dns=" + yaml_quote(option(section, "dns_server", cfg.default_dns))
|
||||
];
|
||||
|
||||
let socks_user = option(section, "socks_user", "");
|
||||
if (socks_user != "")
|
||||
push(uci_cmds, "uci set olcrtc.config.socks_user=" + yaml_quote(socks_user));
|
||||
let socks_pass = option(section, "socks_pass", "");
|
||||
if (socks_pass != "")
|
||||
push(uci_cmds, "uci set olcrtc.config.socks_pass=" + yaml_quote(socks_pass));
|
||||
|
||||
let payload_cmds = payload_uci_commands(connection.payload);
|
||||
for (let cmd in payload_cmds)
|
||||
push(uci_cmds, cmd);
|
||||
|
||||
for (let cmd in uci_cmds)
|
||||
system(cmd);
|
||||
system("uci commit olcrtc");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function write_olcrtc_yaml_config(section, connection) {
|
||||
let yaml = "mode: cnc\n";
|
||||
yaml += "auth:\n";
|
||||
yaml += " provider: " + yaml_quote(connection.provider) + "\n";
|
||||
yaml += "room:\n";
|
||||
yaml += " id: " + yaml_quote(connection.room_id) + "\n";
|
||||
yaml += "crypto:\n";
|
||||
yaml += " key: " + yaml_quote(connection.crypto_key) + "\n";
|
||||
yaml += "net:\n";
|
||||
yaml += " transport: " + yaml_quote(connection.transport) + "\n";
|
||||
yaml += " dns: " + yaml_quote(option(section, "dns_server", cfg.default_dns)) + "\n";
|
||||
yaml += "socks:\n";
|
||||
yaml += " host: " + yaml_quote(option(section, "socks_host", cfg.default_socks_host)) + "\n";
|
||||
yaml += " port: " + yaml_quote(option(section, "socks_port", cfg.default_socks_port)) + "\n";
|
||||
|
||||
let socks_user = option(section, "socks_user", "");
|
||||
if (socks_user != "") {
|
||||
yaml += " username: " + yaml_quote(socks_user) + "\n";
|
||||
yaml += " password: " + yaml_quote(option(section, "socks_pass", "")) + "\n";
|
||||
}
|
||||
|
||||
let tmp = cfg.config_path + ".tmp";
|
||||
let f = fs.open(tmp, "w");
|
||||
if (!f)
|
||||
return false;
|
||||
f.write(yaml);
|
||||
f.close();
|
||||
return rename(tmp, cfg.config_path) == 0;
|
||||
}
|
||||
|
||||
function start_runtime() {
|
||||
let sections = enabled_sections();
|
||||
if (length(sections) == 0) {
|
||||
log_message("No enabled OlcRTC sections found, stopping service");
|
||||
command_success_from_args([cfg.service_init, "stop"]);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!provider_available()) {
|
||||
log_message("OlcRTC provider not available (binary not found: " + cfg.binary + ")", "fatal");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!service_init_exists()) {
|
||||
log_message("OlcRTC service init script not found: " + cfg.service_init, "fatal");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (length(sections) > 1)
|
||||
log_message("Multiple OlcRTC sections found, using first: " + section_name(sections[0]), "warn");
|
||||
|
||||
let section = sections[0];
|
||||
let connection = resolve_connection(section);
|
||||
if (!connection.valid) {
|
||||
log_message("OlcRTC rule '" + section_name(section) + "': " + connection.reason, "fatal");
|
||||
return false;
|
||||
}
|
||||
|
||||
let config_type = fs.stat("/etc/olcrtc/client.yaml") != null ? "yaml" : "uci";
|
||||
|
||||
if (config_type == "yaml") {
|
||||
if (!write_olcrtc_yaml_config(section, connection)) {
|
||||
log_message("Failed to write OlcRTC YAML config", "fatal");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!write_olcrtc_config(section, connection)) {
|
||||
log_message("Failed to write OlcRTC UCI config", "fatal");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!command_success_from_args([cfg.service_init, "restart"])) {
|
||||
log_message("Failed to restart OlcRTC service", "fatal");
|
||||
return false;
|
||||
}
|
||||
|
||||
log_message("OlcRTC provider started successfully (provider=" + connection.provider + ", transport=" + connection.transport + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
function stop_runtime() {
|
||||
command_success_from_args([cfg.service_init, "stop"]);
|
||||
log_message("OlcRTC provider stopped");
|
||||
return true;
|
||||
}
|
||||
|
||||
function status_json() {
|
||||
let installed = provider_available();
|
||||
let running = installed ? service_running() : false;
|
||||
let enabled = installed ? service_enabled() : false;
|
||||
let rule_count = enabled_rule_count();
|
||||
let version = package_version();
|
||||
|
||||
write_json({
|
||||
installed: installed,
|
||||
configured: rule_count > 0,
|
||||
enabled_rule_count: rule_count,
|
||||
service_enabled: enabled,
|
||||
service_running: running,
|
||||
config_path: cfg.config_path,
|
||||
binary: cfg.binary,
|
||||
service_init: cfg.service_init,
|
||||
ready: installed && running && rule_count > 0,
|
||||
status_message: running ? "olcrtc provider status is normal" : (installed ? "olcrtc service is not running" : "olcrtc is not installed")
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
function check_json() {
|
||||
write_json({
|
||||
olcrtc_installed: provider_available(),
|
||||
olcrtc_config_path: cfg.config_path
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
let mode = as_string(ARGV[0]);
|
||||
if (mode == "start-runtime")
|
||||
start_runtime();
|
||||
else if (mode == "stop-runtime")
|
||||
stop_runtime();
|
||||
else if (mode == "status")
|
||||
status_json();
|
||||
else if (mode == "check")
|
||||
check_json();
|
||||
else if (mode == "installed" || mode == "provider-available")
|
||||
exit(provider_available() ? 0 : 1);
|
||||
else if (mode == "package-version")
|
||||
printf("%s\n", package_version());
|
||||
else if (mode == "enabled-rule-count")
|
||||
printf("%d\n", enabled_rule_count());
|
||||
else {
|
||||
warn("Usage: olcrtc/runtime.uc <operation>\n");
|
||||
exit(1);
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
let common = require("core.common");
|
||||
let fs = require("fs");
|
||||
|
||||
let as_string = common.as_string;
|
||||
|
||||
const OLCRTC_PROVIDERS = [ "jitsi", "telemost", "wbstream" ];
|
||||
const OLCRTC_TRANSPORTS = [ "datachannel", "vp8channel", "seichannel", "videochannel" ];
|
||||
|
||||
function yaml_quote(value) {
|
||||
value = as_string(value);
|
||||
if (value == "")
|
||||
return "''";
|
||||
if (match(value, /^[a-zA-Z0-9_./:-]+$/) != null)
|
||||
return value;
|
||||
return "'" + replace(value, "'", "''") + "'";
|
||||
}
|
||||
|
||||
function parse_uri(value) {
|
||||
value = trim(as_string(value));
|
||||
if (substr(value, 0, 9) != "olcrtc://")
|
||||
return { valid: false, reason: "URI must start with olcrtc://" };
|
||||
|
||||
let rest = substr(value, 9);
|
||||
|
||||
let dollar_idx = rindex(rest, "$");
|
||||
let mimo = "";
|
||||
if (dollar_idx >= 0) {
|
||||
mimo = substr(rest, dollar_idx + 1);
|
||||
rest = substr(rest, 0, dollar_idx);
|
||||
}
|
||||
|
||||
let hash_idx = rindex(rest, "#");
|
||||
let crypto_key = "";
|
||||
if (hash_idx >= 0) {
|
||||
crypto_key = substr(rest, hash_idx + 1);
|
||||
rest = substr(rest, 0, hash_idx);
|
||||
}
|
||||
|
||||
let at_idx = index(rest, "@");
|
||||
let room_id = "";
|
||||
let provider_transport = rest;
|
||||
if (at_idx >= 0) {
|
||||
provider_transport = substr(rest, 0, at_idx);
|
||||
room_id = substr(rest, at_idx + 1);
|
||||
}
|
||||
|
||||
let q_idx = index(provider_transport, "?");
|
||||
let provider = "";
|
||||
let transport_with_payload = provider_transport;
|
||||
if (q_idx >= 0) {
|
||||
provider = substr(provider_transport, 0, q_idx);
|
||||
transport_with_payload = substr(provider_transport, q_idx + 1);
|
||||
}
|
||||
|
||||
let payload = "";
|
||||
let transport = transport_with_payload;
|
||||
let lt_idx = index(transport_with_payload, "<");
|
||||
let gt_idx = index(transport_with_payload, ">");
|
||||
if (lt_idx >= 0 && gt_idx > lt_idx) {
|
||||
transport = substr(transport_with_payload, 0, lt_idx);
|
||||
payload = substr(transport_with_payload, lt_idx + 1, gt_idx - lt_idx - 1);
|
||||
}
|
||||
|
||||
if (!array_contains(OLCRTC_PROVIDERS, provider))
|
||||
return { valid: false, reason: "provider must be one of: " + join(", ", OLCRTC_PROVIDERS) };
|
||||
if (!array_contains(OLCRTC_TRANSPORTS, transport))
|
||||
return { valid: false, reason: "transport must be one of: " + join(", ", OLCRTC_TRANSPORTS) };
|
||||
if (room_id == "")
|
||||
return { valid: false, reason: "room_id is required" };
|
||||
if (crypto_key != "" && length(crypto_key) != 64)
|
||||
return { valid: false, reason: "crypto_key must be 64 hex characters" };
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
provider: provider,
|
||||
transport: transport,
|
||||
room_id: room_id,
|
||||
crypto_key: crypto_key,
|
||||
mimo: mimo,
|
||||
payload: payload
|
||||
};
|
||||
}
|
||||
|
||||
function valid_hex_key(value) {
|
||||
value = trim(as_string(value));
|
||||
if (value == "" || value == null)
|
||||
return true;
|
||||
return length(value) == 64 && match(value, /^[0-9a-fA-F]+$/) != null;
|
||||
}
|
||||
|
||||
function valid_number(value, min, max) {
|
||||
value = as_string(value);
|
||||
if (value == "" || value == null)
|
||||
return true;
|
||||
let num = int(value);
|
||||
if (num == null || isNaN(num))
|
||||
return false;
|
||||
return num >= min && num <= max;
|
||||
}
|
||||
|
||||
function valid_url(value) {
|
||||
value = trim(as_string(value));
|
||||
if (value == "" || value == null)
|
||||
return true;
|
||||
return match(value, /^https?:\/\/[^ \t\r\n]+$/) != null;
|
||||
}
|
||||
|
||||
function valid_host_port(value) {
|
||||
value = trim(as_string(value));
|
||||
if (value == "" || value == null)
|
||||
return true;
|
||||
return match(value, /^[^ \t\r\n:]+:[0-9]{1,5}$/) != null;
|
||||
}
|
||||
|
||||
function list_values(value) {
|
||||
if (type(value) == "array")
|
||||
return value;
|
||||
value = trim(as_string(value));
|
||||
if (value == "" || value == null)
|
||||
return [];
|
||||
return split(value, " \t\n\r");
|
||||
}
|
||||
|
||||
function validation_result(valid, message, options) {
|
||||
let result = { valid: valid, message: message || "" };
|
||||
if (options != null)
|
||||
for (let key in options)
|
||||
result[key] = options[key];
|
||||
return result;
|
||||
}
|
||||
|
||||
function validate_section(section) {
|
||||
let errors = [];
|
||||
|
||||
let provider = as_string(section.provider || "");
|
||||
if (provider != "" && !array_contains(OLCRTC_PROVIDERS, provider))
|
||||
errors.push("provider must be one of: " + join(", ", OLCRTC_PROVIDERS));
|
||||
|
||||
let transport = as_string(section.transport || "");
|
||||
if (transport != "" && !array_contains(OLCRTC_TRANSPORTS, transport))
|
||||
errors.push("transport must be one of: " + join(", ", OLCRTC_TRANSPORTS));
|
||||
|
||||
let crypto_key = as_string(section.crypto_key || "");
|
||||
if (crypto_key != "" && !valid_hex_key(crypto_key))
|
||||
errors.push("crypto_key must be 64 hex characters");
|
||||
|
||||
let socks_port = as_string(section.socks_port || "");
|
||||
if (socks_port != "" && !valid_number(socks_port, 1, 65535))
|
||||
errors.push("socks_port must be 1-65535");
|
||||
|
||||
let dns_server = as_string(section.dns_server || "");
|
||||
if (dns_server != "" && !valid_host_port(dns_server))
|
||||
errors.push("dns_server must be in HOST:PORT format");
|
||||
|
||||
let subscription_links = list_values(section.subscription_links);
|
||||
for (let link in subscription_links) {
|
||||
link = trim(as_string(link));
|
||||
if (link == "")
|
||||
continue;
|
||||
if (substr(link, 0, 9) == "olcrtc://") {
|
||||
let parsed = parse_uri(link);
|
||||
if (!parsed.valid)
|
||||
errors.push("invalid olcrtc:// URI: " + parsed.reason);
|
||||
}
|
||||
else if (!valid_url(link))
|
||||
errors.push("subscription link must be olcrtc:// URI or HTTP URL: " + link);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
function validate_section_json() {
|
||||
let input = read_stdin();
|
||||
let data = object_or_empty(json(input));
|
||||
let errors = validate_section(data);
|
||||
if (length(errors) > 0)
|
||||
write_json({ valid: false, message: join("; ", errors) });
|
||||
else
|
||||
write_json({ valid: true, message: "" });
|
||||
return length(errors) == 0;
|
||||
}
|
||||
|
||||
function read_stdin() {
|
||||
let input = fs.open("/dev/stdin", "r");
|
||||
if (!input)
|
||||
return "";
|
||||
let data = input.read("all");
|
||||
input.close();
|
||||
return data == null ? "" : data;
|
||||
}
|
||||
|
||||
function write_json(value) {
|
||||
printf("%s\n", sprintf("%J", value));
|
||||
}
|
||||
|
||||
return {
|
||||
validate_section,
|
||||
validate_section_json,
|
||||
parse_uri,
|
||||
valid_hex_key,
|
||||
providers: OLCRTC_PROVIDERS,
|
||||
transports: OLCRTC_TRANSPORTS
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
let constants = require("core.constants");
|
||||
|
||||
function config(ctx) {
|
||||
let env_get = function(key, fallback) {
|
||||
let val = getenv(key);
|
||||
return val != null ? val : fallback;
|
||||
};
|
||||
|
||||
return {
|
||||
kind: "wdtt",
|
||||
action: "wdtt",
|
||||
config_path: env_get("TACHYON_WDTT_CONFIG", "/etc/config/wdtt"),
|
||||
qwdtt_config_path: env_get("TACHYON_QWDTT_CONFIG", "/etc/qwdtt/config.json"),
|
||||
service_init: env_get("TACHYON_WDTT_SERVICE_INIT", "/etc/init.d/wdtt-client"),
|
||||
qwdtt_service_init: env_get("TACHYON_QWDTT_SERVICE_INIT", "/etc/init.d/qwdtt"),
|
||||
binary: env_get("TACHYON_QWDTT_BIN", "/usr/bin/qwdtt-client"),
|
||||
genlists_bin: env_get("TACHYON_WDTT_GENLISTS_BIN", "/usr/sbin/wdtt-genlists"),
|
||||
resolve_bin: env_get("TACHYON_WDTT_RESOLVE_BIN", "/usr/sbin/wdtt-resolve"),
|
||||
hashes_dir: env_get("TACHYON_WDTT_HASHES_DIR", "/etc/wdtt"),
|
||||
captcha_token_default: "/var/run/qwdtt/captcha.token",
|
||||
default_peer: "YOUR_SERVER:56000",
|
||||
default_workers_qwdtt: "9",
|
||||
default_workers_wdtt: "36",
|
||||
default_max_hashes: "4",
|
||||
default_mode: "selective",
|
||||
default_mtu: "1280",
|
||||
default_refresh: "15m",
|
||||
default_socks_port_base: 1080,
|
||||
package_name: "wdtt",
|
||||
runtime_path: ctx.lib_dir + "/providers/wdtt/runtime.uc",
|
||||
config_name: "tachyon",
|
||||
status_label: "wdtt",
|
||||
check_prefix: "wdtt"
|
||||
};
|
||||
}
|
||||
|
||||
function validator() {
|
||||
return require("providers.wdtt.validator");
|
||||
}
|
||||
|
||||
return { config, validator };
|
||||
@@ -0,0 +1,607 @@
|
||||
#!/usr/bin/env ucode
|
||||
|
||||
let fs = require("fs");
|
||||
let common = require("core.common");
|
||||
let uci_core = require("core.uci");
|
||||
let connections = require("config.connections");
|
||||
|
||||
let as_string = common.as_string;
|
||||
let bool_value = common.bool_value;
|
||||
let object_or_empty = common.object_or_empty;
|
||||
|
||||
const MODE = as_string(getenv("TACHYON_CONFIG_NAME")) || "tachyon";
|
||||
const LIB_DIR = as_string(getenv("TACHYON_LIB_DIR")) || "/usr/lib/tachyon";
|
||||
const CONFIG_NAME = MODE;
|
||||
|
||||
let cfg = require("providers.wdtt.common").config({ lib_dir: LIB_DIR });
|
||||
let wdtt_validator = require("providers.wdtt.validator");
|
||||
|
||||
function log_message(msg, level) {
|
||||
level = level || "info";
|
||||
printf("%s\n", msg);
|
||||
system("logger -t " + MODE + " -p " + (level == "fatal" ? "user.err" : level == "warn" ? "user.warning" : "user.info") + " '" + msg + "'");
|
||||
}
|
||||
|
||||
function command_from_args(args) {
|
||||
let parts = [];
|
||||
for (let arg in args)
|
||||
push(parts, common.shell_quote(as_string(arg)));
|
||||
return join(" ", parts);
|
||||
}
|
||||
|
||||
function command_output(cmd) {
|
||||
let output = trim(popen(cmd, "r") || "");
|
||||
return output;
|
||||
}
|
||||
|
||||
function command_output_from_args(args) {
|
||||
return command_output(command_from_args(args));
|
||||
}
|
||||
|
||||
function command_success_from_args(args) {
|
||||
return system(command_from_args(args)) == 0;
|
||||
}
|
||||
|
||||
function option(section, key, fallback) {
|
||||
let val = section[key];
|
||||
if (val == null)
|
||||
return fallback || "";
|
||||
if (type(val) == "array")
|
||||
return join(" ", val);
|
||||
return as_string(val);
|
||||
}
|
||||
|
||||
function bool_option(section, key, fallback) {
|
||||
let val = section[key];
|
||||
if (val == null)
|
||||
return fallback || false;
|
||||
return bool_value(val);
|
||||
}
|
||||
|
||||
function list_option(section, key) {
|
||||
let val = section[key];
|
||||
if (val == null)
|
||||
return [];
|
||||
if (type(val) == "array")
|
||||
return val;
|
||||
return split(trim(as_string(val)), " \t\n\r");
|
||||
}
|
||||
|
||||
function section_name(section) {
|
||||
return as_string(section[".name"] || "");
|
||||
}
|
||||
|
||||
function read_json_file(path) {
|
||||
let data = fs.readfile(path);
|
||||
if (data == null || data == "")
|
||||
return null;
|
||||
return json(data);
|
||||
}
|
||||
|
||||
function write_json_file(path, data) {
|
||||
let tmp = path + ".tmp";
|
||||
let f = fs.open(tmp, "w");
|
||||
if (!f)
|
||||
return false;
|
||||
f.write(sprintf("%J", data));
|
||||
f.close();
|
||||
return rename(tmp, path) == 0;
|
||||
}
|
||||
|
||||
function lan_mac_address() {
|
||||
for (let iface in ["br-lan", "lan", "eth0", "eth1"]) {
|
||||
let mac_path = "/sys/class/net/" + iface + "/address";
|
||||
let mac = trim(fs.readfile(mac_path) || "");
|
||||
if (mac != "" && mac != "00:00:00:00:00:00")
|
||||
return mac;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function enabled_sections() {
|
||||
return connections.wdtt_sections();
|
||||
}
|
||||
|
||||
function enabled_rule_count() {
|
||||
let sections = enabled_sections();
|
||||
return length(sections);
|
||||
}
|
||||
|
||||
function provider_available() {
|
||||
return fs.stat(cfg.binary) != null || fs.stat(cfg.config_path) != null;
|
||||
}
|
||||
|
||||
function qwdtt_installed() {
|
||||
return fs.stat(cfg.binary) != null;
|
||||
}
|
||||
|
||||
function active_service_init() {
|
||||
if (qwdtt_installed())
|
||||
return cfg.qwdtt_service_init;
|
||||
return cfg.service_init;
|
||||
}
|
||||
|
||||
function service_init_exists() {
|
||||
return fs.stat(active_service_init()) != null;
|
||||
}
|
||||
|
||||
function service_running() {
|
||||
return command_success_from_args([active_service_init(), "status"]);
|
||||
}
|
||||
|
||||
function service_enabled() {
|
||||
return command_success_from_args([active_service_init(), "enabled"]);
|
||||
}
|
||||
|
||||
function package_version() {
|
||||
if (qwdtt_installed()) {
|
||||
let ver = trim(fs.readfile("/etc/qwdtt/.version") || "");
|
||||
if (ver != "")
|
||||
return ver;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function first_http_url(values) {
|
||||
for (let v in values) {
|
||||
v = trim(as_string(v));
|
||||
if (substr(v, 0, 7) == "http://")
|
||||
return v;
|
||||
if (substr(v, 0, 8) == "https://")
|
||||
return v;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function first_local_file(values) {
|
||||
for (let v in values) {
|
||||
v = trim(as_string(v));
|
||||
if (substr(v, 0, 1) == "/")
|
||||
return v;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function first_wdtt_link(values) {
|
||||
for (let v in values) {
|
||||
v = trim(as_string(v));
|
||||
if (substr(v, 0, 7) == "wdtt://")
|
||||
return v;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function write_wdtt_hashes_file(hashes_value) {
|
||||
let dir = cfg.hashes_dir;
|
||||
system("mkdir -p " + common.shell_quote(dir));
|
||||
let path = dir + "/vk-calls.txt";
|
||||
let tmp = path + ".tmp";
|
||||
let f = fs.open(tmp, "w");
|
||||
if (!f)
|
||||
return false;
|
||||
if (type(hashes_value) == "array") {
|
||||
for (let h in hashes_value)
|
||||
f.write(trim(as_string(h)) + "\n");
|
||||
}
|
||||
else {
|
||||
let hashes_str = trim(as_string(hashes_value));
|
||||
if (hashes_str != "") {
|
||||
let parts = split(hashes_str, " ,\n\r\t");
|
||||
for (let h in parts) {
|
||||
h = trim(h);
|
||||
if (h != "")
|
||||
f.write(h + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
f.close();
|
||||
rename(tmp, path);
|
||||
return true;
|
||||
}
|
||||
|
||||
function qwdtt_hashes_list(section) {
|
||||
let hashes = [];
|
||||
let links = list_option(section, "subscription_links");
|
||||
let wdtt_link = first_wdtt_link(links);
|
||||
if (wdtt_link != "") {
|
||||
let parsed = wdtt_validator.parse_wdtt_uri(wdtt_link);
|
||||
if (parsed.valid && parsed.hashes != "") {
|
||||
let parts = split(parsed.hashes, ",");
|
||||
for (let h in parts) {
|
||||
h = trim(h);
|
||||
if (h != "")
|
||||
push(hashes, h);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (length(hashes) == 0) {
|
||||
let hashes_file = first_local_file(links);
|
||||
if (hashes_file != "" && fs.stat(hashes_file) != null) {
|
||||
let data = trim(fs.readfile(hashes_file) || "");
|
||||
if (data != "") {
|
||||
let lines = split(data, "\n\r");
|
||||
for (let line in lines) {
|
||||
line = trim(line);
|
||||
if (line != "")
|
||||
push(hashes, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return hashes;
|
||||
}
|
||||
|
||||
function write_qwdtt_json_config(section, name) {
|
||||
let links = list_option(section, "subscription_links");
|
||||
let wdtt_link = first_wdtt_link(links);
|
||||
let parsed = null;
|
||||
if (wdtt_link != "")
|
||||
parsed = wdtt_validator.parse_wdtt_uri(wdtt_link);
|
||||
|
||||
let peer = option(section, "peer", "");
|
||||
if (peer == "" && parsed != null && parsed.valid)
|
||||
peer = parsed.peer;
|
||||
if (peer == "")
|
||||
peer = cfg.default_peer;
|
||||
|
||||
let password = option(section, "password", "");
|
||||
if (password == "" && parsed != null && parsed.valid)
|
||||
password = parsed.pass;
|
||||
|
||||
let device_id = option(section, "device_id", "");
|
||||
if (device_id == "")
|
||||
device_id = lan_mac_address();
|
||||
|
||||
let workers = option(section, "workers", "");
|
||||
if (workers == "" && parsed != null && parsed.valid && parsed.workers != "")
|
||||
workers = parsed.workers;
|
||||
if (workers == "")
|
||||
workers = cfg.default_workers_qwdtt;
|
||||
|
||||
let hashes = qwdtt_hashes_list(section);
|
||||
let hashes_file = first_local_file(links);
|
||||
if (hashes_file != "" && length(hashes) == 0)
|
||||
write_wdtt_hashes_file([]);
|
||||
|
||||
let qwdtt_mode = option(section, "qwdtt_mode", "rawtun");
|
||||
|
||||
let config = {
|
||||
peer: peer,
|
||||
password: password,
|
||||
device_id: device_id,
|
||||
workers: int(workers) || 9,
|
||||
dns: option(section, "dns", "yandex"),
|
||||
obfs: option(section, "obfs", "audio"),
|
||||
captcha_mode: option(section, "captcha_mode", "auto"),
|
||||
vk_auth: option(section, "vk_auth", "anonymous"),
|
||||
vk_anon_path: option(section, "vk_anon_path", "vkcalls"),
|
||||
no_dtls: bool_option(section, "no_dtls", false),
|
||||
turn_tcp: bool_option(section, "turn_tcp", false),
|
||||
tun_name: option(section, "tun_name", "qwdtt0"),
|
||||
lan_interface: option(section, "lan_iface", "br-lan"),
|
||||
mode: qwdtt_mode
|
||||
};
|
||||
|
||||
if (password != "")
|
||||
config.vk_creds_file = option(section, "vk_creds_file", "");
|
||||
config.captcha_token_file = option(section, "captcha_token_file", cfg.captcha_token_default);
|
||||
|
||||
if (length(hashes) > 0)
|
||||
config.hashes = hashes;
|
||||
else if (hashes_file != "")
|
||||
config.hashes_file = hashes_file;
|
||||
|
||||
if (qwdtt_mode == "socks") {
|
||||
let socks_port = option(section, "socks_port", "");
|
||||
if (socks_port == "" && parsed != null && parsed.valid && parsed.port != "")
|
||||
socks_port = parsed.port;
|
||||
if (socks_port == "")
|
||||
socks_port = as_string(cfg.default_socks_port_base);
|
||||
config.socks = option(section, "socks_addr", "127.0.0.1:" + socks_port);
|
||||
}
|
||||
|
||||
let result = write_json_file(cfg.qwdtt_config_path, config);
|
||||
if (result)
|
||||
system("chmod 0600 " + common.shell_quote(cfg.qwdtt_config_path));
|
||||
|
||||
system("uci set " + MODE + ".wdtt_state.enabled=" + (result ? "1" : "0"));
|
||||
system("uci commit " + MODE);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function write_wdtt_uci_config(section) {
|
||||
let links = list_option(section, "subscription_links");
|
||||
let wdtt_link = first_wdtt_link(links);
|
||||
let parsed = null;
|
||||
if (wdtt_link != "")
|
||||
parsed = wdtt_validator.parse_wdtt_uri(wdtt_link);
|
||||
|
||||
let peer = option(section, "peer", "");
|
||||
if (peer == "" && parsed != null && parsed.valid)
|
||||
peer = parsed.peer;
|
||||
if (peer == "")
|
||||
peer = cfg.default_peer;
|
||||
|
||||
let password = option(section, "password", "");
|
||||
if (password == "" && parsed != null && parsed.valid)
|
||||
password = parsed.pass;
|
||||
|
||||
let device_id = option(section, "device_id", "");
|
||||
if (device_id == "")
|
||||
device_id = lan_mac_address();
|
||||
|
||||
let workers = option(section, "workers", "");
|
||||
if (workers == "" && parsed != null && parsed.valid && parsed.workers != "")
|
||||
workers = parsed.workers;
|
||||
if (workers == "")
|
||||
workers = cfg.default_workers_wdtt;
|
||||
|
||||
let hashes_url = first_http_url(links);
|
||||
let hashes_file = first_local_file(links);
|
||||
|
||||
let uci_cmds = [
|
||||
"uci set wdtt.settings.enabled='1'",
|
||||
"uci set wdtt.settings.peer='" + peer + "'",
|
||||
"uci set wdtt.settings.password='" + password + "'",
|
||||
"uci set wdtt.settings.device_id='" + device_id + "'",
|
||||
"uci set wdtt.settings.workers='" + workers + "'",
|
||||
"uci set wdtt.settings.max_hashes='" + option(section, "max_hashes", cfg.default_max_hashes) + "'",
|
||||
"uci set wdtt.settings.mode='" + option(section, "mode", cfg.default_mode) + "'",
|
||||
"uci set wdtt.settings.mtu='" + option(section, "mtu", cfg.default_mtu) + "'",
|
||||
"uci set wdtt.settings.refresh='" + option(section, "refresh", cfg.default_refresh) + "'",
|
||||
"uci set wdtt.settings.auto_update='" + (bool_option(section, "auto_update", true) ? "1" : "0") + "'",
|
||||
"uci set wdtt.settings.block_doh='" + (bool_option(section, "block_doh", false) ? "1" : "0") + "'",
|
||||
"uci set wdtt.settings.block_ipv6='" + (bool_option(section, "block_ipv6", false) ? "1" : "0") + "'"
|
||||
];
|
||||
|
||||
if (hashes_url != "")
|
||||
push(uci_cmds, "uci set wdtt.settings.hashes_url='" + hashes_url + "'");
|
||||
if (hashes_file != "")
|
||||
push(uci_cmds, "uci set wdtt.settings.hashes_file='" + hashes_file + "'");
|
||||
|
||||
let port = "";
|
||||
if (parsed != null && parsed.valid && parsed.port != "")
|
||||
port = parsed.port;
|
||||
if (port != "")
|
||||
push(uci_cmds, "uci set wdtt.settings.listen='127.0.0.1:" + port + "'");
|
||||
|
||||
for (let cmd in uci_cmds)
|
||||
system(cmd);
|
||||
system("uci commit wdtt");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function udp_port_in_use(port) {
|
||||
let hex_port = sprintf("%04X", port);
|
||||
for (let proc_file in ["/proc/net/udp", "/proc/net/udp6"]) {
|
||||
let data = fs.readfile(proc_file);
|
||||
if (data == null)
|
||||
continue;
|
||||
let lines = split(data, "\n");
|
||||
for (let i = 1; i < length(lines); i++) {
|
||||
let parts = split(trim(lines[i]), " \t");
|
||||
if (length(parts) >= 2) {
|
||||
let local = parts[1];
|
||||
let colon_idx = index(local, ":");
|
||||
if (colon_idx >= 0) {
|
||||
let port_hex = substr(local, colon_idx + 1);
|
||||
if (port_hex == hex_port)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function restart_qwdtt_service() {
|
||||
command_success_from_args([cfg.qwdtt_service_init, "stop"]);
|
||||
|
||||
let wait_ms = 0;
|
||||
while (wait_ms < 5000) {
|
||||
if (command_output_from_args(["pidof", "qwdtt-client"]) == "")
|
||||
break;
|
||||
system("sleep 0.1");
|
||||
wait_ms += 100;
|
||||
}
|
||||
if (wait_ms >= 5000)
|
||||
system("killall -9 qwdtt-client 2>/dev/null || true");
|
||||
|
||||
let port = cfg.default_socks_port_base;
|
||||
wait_ms = 0;
|
||||
while (wait_ms < 10000) {
|
||||
if (!udp_port_in_use(port))
|
||||
break;
|
||||
system("sleep 0.5");
|
||||
wait_ms += 500;
|
||||
}
|
||||
|
||||
system("sleep 0.5");
|
||||
return command_success_from_args([cfg.qwdtt_service_init, "start"]);
|
||||
}
|
||||
|
||||
function start_runtime() {
|
||||
let sections = enabled_sections();
|
||||
if (length(sections) == 0) {
|
||||
log_message("No enabled WDTT sections found, disabling provider");
|
||||
if (qwdtt_installed()) {
|
||||
system("uci set " + MODE + ".wdtt_state.enabled='0'");
|
||||
system("uci commit " + MODE);
|
||||
command_success_from_args([cfg.qwdtt_service_init, "stop"]);
|
||||
}
|
||||
else {
|
||||
system("uci set wdtt.settings.enabled='0'");
|
||||
system("uci commit wdtt");
|
||||
command_success_from_args([cfg.service_init, "stop"]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!provider_available()) {
|
||||
log_message("WDTT provider not available (no binary or config found)", "fatal");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!service_init_exists()) {
|
||||
log_message("WDTT service init script not found: " + active_service_init(), "fatal");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (length(sections) > 1)
|
||||
log_message("Multiple WDTT sections found, using first: " + section_name(sections[0]), "warn");
|
||||
|
||||
let section = sections[0];
|
||||
|
||||
if (qwdtt_installed()) {
|
||||
if (!write_qwdtt_json_config(section, section_name(section))) {
|
||||
log_message("Failed to write qwdtt config", "fatal");
|
||||
return false;
|
||||
}
|
||||
if (!restart_qwdtt_service()) {
|
||||
log_message("Failed to start qwdtt service", "fatal");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!write_wdtt_uci_config(section)) {
|
||||
log_message("Failed to write wdtt UCI config", "fatal");
|
||||
return false;
|
||||
}
|
||||
if (!command_success_from_args([cfg.service_init, "restart"])) {
|
||||
log_message("Failed to restart wdtt-client service", "fatal");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (fs.stat(cfg.genlists_bin) != null)
|
||||
command_output_from_args([cfg.genlists_bin]);
|
||||
if (fs.stat(cfg.resolve_bin) != null)
|
||||
command_output_from_args([cfg.resolve_bin]);
|
||||
|
||||
log_message("WDTT provider started successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
function stop_runtime() {
|
||||
if (qwdtt_installed()) {
|
||||
system("uci set " + MODE + ".wdtt_state.enabled='0'");
|
||||
system("uci commit " + MODE);
|
||||
command_success_from_args([cfg.qwdtt_service_init, "stop"]);
|
||||
}
|
||||
else {
|
||||
system("uci set wdtt.settings.enabled='0'");
|
||||
system("uci commit wdtt");
|
||||
command_success_from_args([cfg.service_init, "stop"]);
|
||||
}
|
||||
log_message("WDTT provider stopped");
|
||||
return true;
|
||||
}
|
||||
|
||||
function status_json() {
|
||||
let installed = provider_available();
|
||||
let running = installed ? service_running() : false;
|
||||
let enabled = installed ? service_enabled() : false;
|
||||
let rule_count = enabled_rule_count();
|
||||
let client = qwdtt_installed() ? "qwdtt" : "wdtt-openwrt";
|
||||
let config_path = qwdtt_installed() ? cfg.qwdtt_config_path : cfg.config_path;
|
||||
let version = package_version();
|
||||
|
||||
write_json({
|
||||
installed: installed,
|
||||
configured: rule_count > 0,
|
||||
enabled_rule_count: rule_count,
|
||||
service_enabled: enabled,
|
||||
service_running: running,
|
||||
config_path: config_path,
|
||||
service_init: active_service_init(),
|
||||
client: client,
|
||||
package_version: version,
|
||||
ready: installed && running && rule_count > 0,
|
||||
status_message: running ? "wdtt provider status is normal" : (installed ? "wdtt service is not running" : "wdtt is not installed")
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
function check_json() {
|
||||
write_json({
|
||||
wdtt_installed: provider_available(),
|
||||
wdtt_config_path: cfg.config_path,
|
||||
qwdtt_installed: qwdtt_installed(),
|
||||
qwdtt_version: qwdtt_installed() ? package_version() : ""
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
function captcha_token_file() {
|
||||
if (qwdtt_installed()) {
|
||||
let config = read_json_file(cfg.qwdtt_config_path);
|
||||
if (config != null && config.captcha_token_file != null)
|
||||
return config.captcha_token_file;
|
||||
}
|
||||
return cfg.captcha_token_default;
|
||||
}
|
||||
|
||||
function wdtt_captcha_status() {
|
||||
let running = service_running();
|
||||
let token_file = captcha_token_file();
|
||||
let has_token = fs.stat(token_file) != null;
|
||||
let token = "";
|
||||
if (has_token)
|
||||
token = trim(fs.readfile(token_file) || "");
|
||||
|
||||
write_json({
|
||||
running: running,
|
||||
token_file: token_file,
|
||||
has_token: has_token,
|
||||
token: token
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
function wdtt_captcha_submit(token) {
|
||||
let token_file = captcha_token_file();
|
||||
let dir = "";
|
||||
let last_slash = rindex(token_file, "/");
|
||||
if (last_slash >= 0)
|
||||
dir = substr(token_file, 0, last_slash);
|
||||
if (dir != "")
|
||||
system("mkdir -p " + common.shell_quote(dir));
|
||||
|
||||
let f = fs.open(token_file, "w");
|
||||
if (!f) {
|
||||
write_json({ success: false, error: "Failed to write token file" });
|
||||
return false;
|
||||
}
|
||||
f.write(trim(as_string(token)));
|
||||
f.close();
|
||||
system("chmod 0600 " + common.shell_quote(token_file));
|
||||
|
||||
write_json({ success: true, path: token_file });
|
||||
return true;
|
||||
}
|
||||
|
||||
let mode = as_string(ARGV[0]);
|
||||
if (mode == "start-runtime")
|
||||
start_runtime();
|
||||
else if (mode == "stop-runtime")
|
||||
stop_runtime();
|
||||
else if (mode == "status")
|
||||
status_json();
|
||||
else if (mode == "check")
|
||||
check_json();
|
||||
else if (mode == "installed" || mode == "provider-available")
|
||||
exit(provider_available() ? 0 : 1);
|
||||
else if (mode == "package-version")
|
||||
printf("%s\n", package_version());
|
||||
else if (mode == "enabled-rule-count")
|
||||
printf("%d\n", enabled_rule_count());
|
||||
else if (mode == "wdtt-captcha-status")
|
||||
wdtt_captcha_status();
|
||||
else if (mode == "wdtt-captcha-submit")
|
||||
wdtt_captcha_submit(ARGV[1]);
|
||||
else {
|
||||
warn("Usage: wdtt/runtime.uc <operation>\n");
|
||||
exit(1);
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
let common = require("core.common");
|
||||
let fs = require("fs");
|
||||
|
||||
let as_string = common.as_string;
|
||||
|
||||
const WDTT_MODES = [ "selective", "lan-all", "full" ];
|
||||
const QWDTT_MODES = [ "rawtun", "socks", "vpn" ];
|
||||
const WDTT_COMMUNITY_LISTS = [
|
||||
"russia-inside", "russia-outside", "ukraine", "telegram", "meta",
|
||||
"youtube", "discord", "tiktok", "twitter", "hdrezka", "roblox",
|
||||
"cloudflare", "cloudfront", "google_ai", "google_meet", "google_play",
|
||||
"hetzner", "ovh", "digitalocean", "anime", "news", "geoblock",
|
||||
"block", "porn", "hodca"
|
||||
];
|
||||
|
||||
function url_decode(value) {
|
||||
let result = "";
|
||||
let len = length(value);
|
||||
let i = 0;
|
||||
while (i < len) {
|
||||
if (value[i] == '%' && i + 2 < len) {
|
||||
let hex = substr(value, i + 1, 2);
|
||||
let code = parseInt(hex, 16);
|
||||
if (code != null && !isNaN(code))
|
||||
result += chr(code);
|
||||
else
|
||||
result += '%';
|
||||
i += 3;
|
||||
}
|
||||
else if (value[i] == '+') {
|
||||
result += ' ';
|
||||
i += 1;
|
||||
}
|
||||
else {
|
||||
result += value[i];
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function parse_query_string(query) {
|
||||
let params = {};
|
||||
if (query == "" || query == null)
|
||||
return params;
|
||||
let pairs = split(query, "&");
|
||||
for (let pair in pairs) {
|
||||
let kv = split(pair, "=", 2);
|
||||
if (length(kv) == 2)
|
||||
params[kv[0]] = url_decode(kv[1]);
|
||||
else if (length(kv) == 1)
|
||||
params[kv[0]] = "";
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
function parse_wdtt_uri(value) {
|
||||
value = trim(as_string(value));
|
||||
if (substr(value, 0, 7) != "wdtt://")
|
||||
return { valid: false, reason: "URI must start with wdtt://" };
|
||||
|
||||
let rest = substr(value, 7);
|
||||
let hash_idx = index(rest, "#");
|
||||
let label = "";
|
||||
if (hash_idx >= 0) {
|
||||
label = substr(rest, hash_idx + 1);
|
||||
rest = substr(rest, 0, hash_idx);
|
||||
}
|
||||
|
||||
let params = parse_query_string(rest);
|
||||
let peer = params.peer || "";
|
||||
let hashes = params.hashes || "";
|
||||
let pass = params.pass || "";
|
||||
let workers = params.workers || "";
|
||||
let port = params.port || "";
|
||||
|
||||
if (peer == "")
|
||||
return { valid: false, reason: "wdtt:// URI must contain peer parameter" };
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
peer: peer,
|
||||
hashes: hashes,
|
||||
pass: pass,
|
||||
workers: workers,
|
||||
port: port,
|
||||
label: label
|
||||
};
|
||||
}
|
||||
|
||||
function valid_peer(value) {
|
||||
value = trim(as_string(value));
|
||||
if (value == "" || value == null)
|
||||
return true;
|
||||
return match(value, /^[^ \t\r\n:]+:[0-9]{1,5}$/) != null;
|
||||
}
|
||||
|
||||
function valid_url(value) {
|
||||
value = trim(as_string(value));
|
||||
if (value == "" || value == null)
|
||||
return true;
|
||||
return match(value, /^https?:\/\/[^ \t\r\n]+$/) != null;
|
||||
}
|
||||
|
||||
function valid_number(value, min, max) {
|
||||
value = as_string(value);
|
||||
if (value == "" || value == null)
|
||||
return true;
|
||||
let num = int(value);
|
||||
if (num == null || isNaN(num))
|
||||
return false;
|
||||
return num >= min && num <= max;
|
||||
}
|
||||
|
||||
function valid_mode(value) {
|
||||
if (value == "" || value == null)
|
||||
return true;
|
||||
return array_contains(WDTT_MODES, value);
|
||||
}
|
||||
|
||||
function valid_qwdtt_mode(value) {
|
||||
if (value == "" || value == null)
|
||||
return true;
|
||||
return array_contains(QWDTT_MODES, value);
|
||||
}
|
||||
|
||||
function valid_community_list(value) {
|
||||
return array_contains(WDTT_COMMUNITY_LISTS, value);
|
||||
}
|
||||
|
||||
function list_values(value) {
|
||||
if (type(value) == "array")
|
||||
return value;
|
||||
value = trim(as_string(value));
|
||||
if (value == "" || value == null)
|
||||
return [];
|
||||
return split(value, " \t\n\r");
|
||||
}
|
||||
|
||||
function validation_result(valid, message, options) {
|
||||
let result = { valid: valid, message: message || "" };
|
||||
if (options != null)
|
||||
for (let key in options)
|
||||
result[key] = options[key];
|
||||
return result;
|
||||
}
|
||||
|
||||
function validate_section(section) {
|
||||
let errors = [];
|
||||
|
||||
let peer = as_string(section.peer || "");
|
||||
if (peer != "" && !valid_peer(peer))
|
||||
errors.push("peer must be in HOST:PORT format");
|
||||
|
||||
let mode = as_string(section.mode || "");
|
||||
if (!valid_mode(mode))
|
||||
errors.push("mode must be one of: " + join(", ", WDTT_MODES));
|
||||
|
||||
let qwdtt_mode = as_string(section.qwdtt_mode || "");
|
||||
if (!valid_qwdtt_mode(qwdtt_mode))
|
||||
errors.push("qwdtt_mode must be one of: " + join(", ", QWDTT_MODES));
|
||||
|
||||
let workers = as_string(section.workers || "");
|
||||
if (workers != "" && !valid_number(workers, 1, 1024))
|
||||
errors.push("workers must be 1-1024");
|
||||
|
||||
let max_hashes = as_string(section.max_hashes || "");
|
||||
if (max_hashes != "" && !valid_number(max_hashes, 1, 64))
|
||||
errors.push("max_hashes must be 1-64");
|
||||
|
||||
let mtu = as_string(section.mtu || "");
|
||||
if (mtu != "" && !valid_number(mtu, 576, 1500))
|
||||
errors.push("mtu must be 576-1500");
|
||||
|
||||
let subscription_links = list_values(section.subscription_links);
|
||||
for (let link in subscription_links) {
|
||||
link = trim(as_string(link));
|
||||
if (link == "")
|
||||
continue;
|
||||
if (substr(link, 0, 7) == "wdtt://") {
|
||||
let parsed = parse_wdtt_uri(link);
|
||||
if (!parsed.valid)
|
||||
errors.push("invalid wdtt:// URI: " + parsed.reason);
|
||||
}
|
||||
else if (substr(link, 0, 1) == "/") {
|
||||
/* local file — ok */
|
||||
}
|
||||
else if (!valid_url(link))
|
||||
errors.push("subscription link must be HTTP URL, wdtt:// URI, or local file path: " + link);
|
||||
}
|
||||
|
||||
let community_lists = list_values(section.community_lists);
|
||||
for (let cl in community_lists) {
|
||||
if (!valid_community_list(cl))
|
||||
errors.push("unknown community list: " + cl);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
function validate_section_json() {
|
||||
let input = read_stdin();
|
||||
let data = object_or_empty(json(input));
|
||||
let errors = validate_section(data);
|
||||
if (length(errors) > 0)
|
||||
write_json({ valid: false, message: join("; ", errors) });
|
||||
else
|
||||
write_json({ valid: true, message: "" });
|
||||
return length(errors) == 0;
|
||||
}
|
||||
|
||||
function read_stdin() {
|
||||
let input = fs.open("/dev/stdin", "r");
|
||||
if (!input)
|
||||
return "";
|
||||
let data = input.read("all");
|
||||
input.close();
|
||||
return data == null ? "" : data;
|
||||
}
|
||||
|
||||
function write_json(value) {
|
||||
printf("%s\n", sprintf("%J", value));
|
||||
}
|
||||
|
||||
return {
|
||||
validate_section,
|
||||
validate_section_json,
|
||||
parse_wdtt_uri,
|
||||
url_decode,
|
||||
valid_peer,
|
||||
valid_mode,
|
||||
valid_qwdtt_mode,
|
||||
valid_url,
|
||||
valid_community_list,
|
||||
list_values,
|
||||
modes: WDTT_MODES,
|
||||
qwdtt_modes: QWDTT_MODES,
|
||||
community_lists: WDTT_COMMUNITY_LISTS
|
||||
};
|
||||
@@ -63,6 +63,7 @@ function candidates() {
|
||||
let action = option(section, "action", "");
|
||||
if (action == "connection" || action == "proxy" || action == "outbound" || action == "vpn" ||
|
||||
action == "awg" || action == "warp" || action == "byedpi" || action == "zapret" || action == "zapret2" ||
|
||||
action == "wdtt" || action == "olcrtc" ||
|
||||
action == "anytls" || action == "snell" || action == "mieru" || action == "sudoku" ||
|
||||
action == "masque" || action == "openvpn")
|
||||
push(result, as_string(section[".name"]));
|
||||
|
||||
@@ -11,6 +11,9 @@ let strings = {
|
||||
bootstrap_dns_server: "Bootstrap DNS",
|
||||
dns_strategy: "DNS Strategy",
|
||||
dns_detour_enabled: "DNS Detour",
|
||||
routed_dns_enabled: "Routed DNS",
|
||||
routed_dns_type: "Routed DNS Protocol",
|
||||
routed_dns_server: "Routed DNS Servers",
|
||||
source_network_interfaces: "Inbound Interfaces",
|
||||
enable_output_network_interface: "Bind to WAN",
|
||||
enable_badwan_interface_monitoring: "WAN Monitoring",
|
||||
@@ -561,6 +564,9 @@ let strings = {
|
||||
bootstrap_dns_server: "Bootstrap DNS",
|
||||
dns_strategy: "Стратегия DNS",
|
||||
dns_detour_enabled: "DNS Detour",
|
||||
routed_dns_enabled: "Маршрутизированный DNS",
|
||||
routed_dns_type: "Протокол маршрутизированного DNS",
|
||||
routed_dns_server: "Серверы маршрутизированного DNS",
|
||||
source_network_interfaces: "Входящие интерфейсы",
|
||||
enable_output_network_interface: "Привязка к WAN",
|
||||
enable_badwan_interface_monitoring: "Мониторинг WAN",
|
||||
|
||||
@@ -120,6 +120,8 @@ const ZAPRET_UC = LIB_DIR + "/providers/zapret/runtime.uc";
|
||||
const ZAPRET2_UC = LIB_DIR + "/providers/zapret2/runtime.uc";
|
||||
const BYEDPI_UC = LIB_DIR + "/providers/byedpi/runtime.uc";
|
||||
const TAILSCALE_UC = LIB_DIR + "/providers/tailscale/runtime.uc";
|
||||
const WDTT_UC = LIB_DIR + "/providers/wdtt/runtime.uc";
|
||||
const OLCRTC_UC = LIB_DIR + "/providers/olcrtc/runtime.uc";
|
||||
const PARENTAL_QUOTA_UC = LIB_DIR + "/service/parental_quota.uc";
|
||||
const PACKAGES_UC = LIB_DIR + "/core/packages.uc";
|
||||
const WATCHDOG_UC = LIB_DIR + "/service/watchdog.uc";
|
||||
@@ -935,7 +937,10 @@ function start_main() {
|
||||
release_start_subscription_update_lock();
|
||||
module_success(ZAPRET_UC, [ "start-runtime" ]);
|
||||
module_success(ZAPRET2_UC, [ "start-runtime" ]);
|
||||
module_success(WDTT_UC, [ "start-runtime" ]);
|
||||
module_success(OLCRTC_UC, [ "start-runtime" ]);
|
||||
|
||||
module_background(UI_UC, [ "latency-boot-sweep" ]);
|
||||
module_background(UPDATES_UC, [ "list-update" ]);
|
||||
module_background(LIB_DIR + "/service/lifecycle.uc", [ "discover-awg-mtu" ]);
|
||||
return 0;
|
||||
@@ -1021,6 +1026,10 @@ function stop_main() {
|
||||
log_message("Zapret2 stop failed (non-fatal)", "warn");
|
||||
if (!module_success(BYEDPI_UC, [ "stop-runtime" ]))
|
||||
log_message("ByeDPI stop failed (non-fatal)", "warn");
|
||||
if (!module_success(WDTT_UC, [ "stop-runtime" ]))
|
||||
log_message("WDTT stop failed (non-fatal)", "warn");
|
||||
if (!module_success(OLCRTC_UC, [ "stop-runtime" ]))
|
||||
log_message("OlcRTC stop failed (non-fatal)", "warn");
|
||||
if (!module_success(TAILSCALE_UC, [ "stop-runtime" ]))
|
||||
log_message("Tailscale stop failed (non-fatal)", "warn");
|
||||
module_success(PARENTAL_QUOTA_UC, [ "remove-cron" ]);
|
||||
@@ -1209,6 +1218,8 @@ function parse_reload_plan(output) {
|
||||
needs_zapret_restart: 0,
|
||||
needs_zapret2_restart: 0,
|
||||
needs_byedpi_restart: 0,
|
||||
needs_wdtt_restart: 0,
|
||||
needs_olcrtc_restart: 0,
|
||||
needs_dnsmasq_configure: 0,
|
||||
needs_dnsmasq_restore: 0,
|
||||
needs_cron_refresh: 0,
|
||||
@@ -1245,6 +1256,8 @@ function reload_actions_summary(plan) {
|
||||
actions = append_reload_action(actions, plan.needs_zapret_restart, "Zapret");
|
||||
actions = append_reload_action(actions, plan.needs_zapret2_restart, "Zapret2");
|
||||
actions = append_reload_action(actions, plan.needs_byedpi_restart, "ByeDPI");
|
||||
actions = append_reload_action(actions, plan.needs_wdtt_restart, "WDTT");
|
||||
actions = append_reload_action(actions, plan.needs_olcrtc_restart, "OlcRTC");
|
||||
actions = append_reload_action(actions, plan.needs_dnsmasq_configure || plan.needs_dnsmasq_restore, "dnsmasq");
|
||||
actions = append_reload_action(actions, plan.needs_cron_refresh, "scheduled jobs");
|
||||
actions = append_reload_action(actions, plan.needs_list_update, "remote lists");
|
||||
@@ -1435,6 +1448,10 @@ function reload(reason) {
|
||||
module_success(ZAPRET2_UC, [ "stop-runtime" ]);
|
||||
if (plan.needs_byedpi_restart == 1)
|
||||
module_success(BYEDPI_UC, [ "stop-runtime" ]);
|
||||
if (plan.needs_wdtt_restart == 1)
|
||||
module_success(WDTT_UC, [ "stop-runtime" ]);
|
||||
if (plan.needs_olcrtc_restart == 1)
|
||||
module_success(OLCRTC_UC, [ "stop-runtime" ]);
|
||||
|
||||
if (plan.needs_hosts_update == 1 && module_success(STATE_UC, [ "has-hosts-list-update-sources" ]))
|
||||
module_success(HOSTS_UC, [ "list-update" ]);
|
||||
@@ -1503,6 +1520,10 @@ function reload(reason) {
|
||||
module_success(ZAPRET_UC, [ "start-runtime" ]);
|
||||
if (plan.needs_zapret2_restart == 1)
|
||||
module_success(ZAPRET2_UC, [ "start-runtime" ]);
|
||||
if (plan.needs_wdtt_restart == 1)
|
||||
module_success(WDTT_UC, [ "start-runtime" ]);
|
||||
if (plan.needs_olcrtc_restart == 1)
|
||||
module_success(OLCRTC_UC, [ "start-runtime" ]);
|
||||
if (plan.needs_byedpi_restart == 1)
|
||||
module_success(BYEDPI_UC, [ "start-runtime" ]);
|
||||
// Native Tailscale must be up before sing-box so tailnet routes win over
|
||||
|
||||
@@ -63,6 +63,8 @@ function emit_reload_plan(previous, current, context) {
|
||||
zapret2_queue: current.zapret2_queue != previous.zapret2_queue,
|
||||
zapret2_runtime: current.zapret2_runtime != previous.zapret2_runtime,
|
||||
byedpi_runtime: current.byedpi_runtime != previous.byedpi_runtime,
|
||||
wdtt_runtime: current.wdtt_runtime != previous.wdtt_runtime,
|
||||
olcrtc_runtime: current.olcrtc_runtime != previous.olcrtc_runtime,
|
||||
list: current.list != previous.list,
|
||||
hosts_list: current.hosts_list != previous.hosts_list,
|
||||
cron: current.cron != previous.cron
|
||||
@@ -74,6 +76,8 @@ function emit_reload_plan(previous, current, context) {
|
||||
zapret_restart: false,
|
||||
zapret2_restart: false,
|
||||
byedpi_restart: false,
|
||||
wdtt_restart: false,
|
||||
olcrtc_restart: false,
|
||||
dnsmasq_configure: false,
|
||||
dnsmasq_restore: false,
|
||||
cron_refresh: false,
|
||||
@@ -112,6 +116,12 @@ function emit_reload_plan(previous, current, context) {
|
||||
if (changed.byedpi_runtime)
|
||||
needs.byedpi_restart = true;
|
||||
|
||||
if (changed.wdtt_runtime)
|
||||
needs.wdtt_restart = true;
|
||||
|
||||
if (changed.olcrtc_runtime)
|
||||
needs.olcrtc_restart = true;
|
||||
|
||||
if (changed.dnsmasq) {
|
||||
if (!current.dont_touch_dhcp)
|
||||
needs.dnsmasq_configure = true;
|
||||
@@ -175,6 +185,8 @@ function emit_reload_plan(previous, current, context) {
|
||||
emit_bool("changed_zapret2_queue", changed.zapret2_queue);
|
||||
emit_bool("changed_zapret2_runtime", changed.zapret2_runtime);
|
||||
emit_bool("changed_byedpi_runtime", changed.byedpi_runtime);
|
||||
emit_bool("changed_wdtt_runtime", changed.wdtt_runtime);
|
||||
emit_bool("changed_olcrtc_runtime", changed.olcrtc_runtime);
|
||||
emit_bool("changed_cron", changed.cron);
|
||||
emit_bool("changed_list", changed.list);
|
||||
emit_bool("changed_hosts_list", changed.hosts_list);
|
||||
@@ -184,6 +196,8 @@ function emit_reload_plan(previous, current, context) {
|
||||
emit_bool("needs_zapret_restart", needs.zapret_restart);
|
||||
emit_bool("needs_zapret2_restart", needs.zapret2_restart);
|
||||
emit_bool("needs_byedpi_restart", needs.byedpi_restart);
|
||||
emit_bool("needs_wdtt_restart", needs.wdtt_restart);
|
||||
emit_bool("needs_olcrtc_restart", needs.olcrtc_restart);
|
||||
emit_bool("needs_dnsmasq_configure", needs.dnsmasq_configure);
|
||||
emit_bool("needs_dnsmasq_restore", needs.dnsmasq_restore);
|
||||
emit_bool("needs_cron_refresh", needs.cron_refresh);
|
||||
|
||||
@@ -138,6 +138,8 @@ const RELOAD_STATE_FIELDS = [
|
||||
"zapret2_queue_signature",
|
||||
"zapret2_runtime_signature",
|
||||
"byedpi_runtime_signature",
|
||||
"wdtt_runtime_signature",
|
||||
"olcrtc_runtime_signature",
|
||||
"list_signature",
|
||||
"hosts_list_signature",
|
||||
"cron_signature",
|
||||
@@ -1008,6 +1010,9 @@ function nft_runtime_signature_body(settings, sections, schedules, profiles, gue
|
||||
body = signature_add_value(body, "rule." + name + ".fully_routed_ips", option(section, "fully_routed_ips", ""));
|
||||
body = signature_add_value(body, "rule." + name + ".excluded_ips", option(section, "excluded_ips", ""));
|
||||
body = signature_add_value(body, "rule." + name + ".excluded_protocol", option(section, "excluded_protocol", ""));
|
||||
body = signature_add_value(body, "rule." + name + ".routed_dns_enabled", bool_option(section, "routed_dns_enabled", false) ? "1" : "0");
|
||||
body = signature_add_value(body, "rule." + name + ".routed_dns_type", option(section, "routed_dns_type", ""));
|
||||
body = signature_add_value(body, "rule." + name + ".routed_dns_server", option(section, "routed_dns_server", ""));
|
||||
body = signature_add_value(body, "rule." + name + ".protocol", option(section, "protocol", ""));
|
||||
let comm_subnets = bool_option(section, "community_subnets", true) ? rule_config.filter_community_subnet_lists_value(connections.community_lists_value(section)) : "";
|
||||
body = signature_add_value(body, "rule." + name + ".community_subnet_lists", comm_subnets);
|
||||
@@ -1781,6 +1786,57 @@ function byedpi_runtime_signature_body(sections) {
|
||||
return body;
|
||||
}
|
||||
|
||||
function wdtt_runtime_signature_body(sections) {
|
||||
let body = "";
|
||||
|
||||
for (let section in sections) {
|
||||
section = object_or_empty(section);
|
||||
if (!section_action_is_enabled(section, "wdtt"))
|
||||
continue;
|
||||
|
||||
let name = section_name(section);
|
||||
body = signature_add_value(body, "wdtt." + name + ".peer", option(section, "peer", ""));
|
||||
body = signature_add_value(body, "wdtt." + name + ".password", option(section, "password", ""));
|
||||
body = signature_add_value(body, "wdtt." + name + ".device_id", option(section, "device_id", ""));
|
||||
body = signature_add_value(body, "wdtt." + name + ".mode", option(section, "mode", ""));
|
||||
body = signature_add_value(body, "wdtt." + name + ".workers", option(section, "workers", ""));
|
||||
body = signature_add_value(body, "wdtt." + name + ".max_hashes", option(section, "max_hashes", ""));
|
||||
body = signature_add_value(body, "wdtt." + name + ".mtu", option(section, "mtu", ""));
|
||||
body = signature_add_value(body, "wdtt." + name + ".refresh", option(section, "refresh", ""));
|
||||
body = signature_add_value(body, "wdtt." + name + ".auto_update", bool_option(section, "auto_update", true) ? "1" : "0");
|
||||
body = signature_add_value(body, "wdtt." + name + ".block_doh", bool_option(section, "block_doh", false) ? "1" : "0");
|
||||
body = signature_add_value(body, "wdtt." + name + ".block_ipv6", bool_option(section, "block_ipv6", false) ? "1" : "0");
|
||||
body = signature_add_value(body, "wdtt." + name + ".subscription_links", join(" ", list_option(section, "subscription_links")));
|
||||
body = signature_add_value(body, "wdtt." + name + ".community_lists", join(" ", list_option(section, "community_lists")));
|
||||
body = signature_add_value(body, "wdtt." + name + ".remote_domain_list", join(" ", list_option(section, "remote_domain_list")));
|
||||
body = signature_add_value(body, "wdtt." + name + ".qwdtt_mode", option(section, "qwdtt_mode", ""));
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
function olcrtc_runtime_signature_body(sections) {
|
||||
let body = "";
|
||||
|
||||
for (let section in sections) {
|
||||
section = object_or_empty(section);
|
||||
if (!section_action_is_enabled(section, "olcrtc"))
|
||||
continue;
|
||||
|
||||
let name = section_name(section);
|
||||
body = signature_add_value(body, "olcrtc." + name + ".provider", option(section, "provider", ""));
|
||||
body = signature_add_value(body, "olcrtc." + name + ".transport", option(section, "transport", ""));
|
||||
body = signature_add_value(body, "olcrtc." + name + ".room_id", option(section, "room_id", ""));
|
||||
body = signature_add_value(body, "olcrtc." + name + ".crypto_key", option(section, "crypto_key", ""));
|
||||
body = signature_add_value(body, "olcrtc." + name + ".socks_host", option(section, "socks_host", ""));
|
||||
body = signature_add_value(body, "olcrtc." + name + ".socks_port", option(section, "socks_port", ""));
|
||||
body = signature_add_value(body, "olcrtc." + name + ".dns_server", option(section, "dns_server", ""));
|
||||
body = signature_add_value(body, "olcrtc." + name + ".subscription_links", join(" ", list_option(section, "subscription_links")));
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
function reload_state_values_from_sources(format, settings, sections, servers, dnsmasq, legacy_dnsmasq_present, mwan3_active_value, schedules, profiles, guest_modes) {
|
||||
return {
|
||||
format: as_string(format),
|
||||
@@ -1793,6 +1849,8 @@ function reload_state_values_from_sources(format, settings, sections, servers, d
|
||||
zapret2_queue_signature: signature_hash(action_queue_signature_body(sections, "zapret2", "zapret2_queue.section")),
|
||||
zapret2_runtime_signature: signature_hash(zapret2_runtime_signature_body(sections)),
|
||||
byedpi_runtime_signature: signature_hash(byedpi_runtime_signature_body(sections)),
|
||||
wdtt_runtime_signature: signature_hash(wdtt_runtime_signature_body(sections)),
|
||||
olcrtc_runtime_signature: signature_hash(olcrtc_runtime_signature_body(sections)),
|
||||
list_signature: signature_hash(list_update_signature_body(sections)),
|
||||
hosts_list_signature: signature_hash(hosts_list_signature_body(sections)),
|
||||
cron_signature: signature_hash(cron_signature_body(settings, sections)),
|
||||
@@ -2189,6 +2247,18 @@ else if (mode == "byedpi-runtime-signature-fixture") {
|
||||
let data = fixture_data(ARGV[1]);
|
||||
exit(print_signature_hash(byedpi_runtime_signature_body(fixture_section_list(data))) ? 0 : 1);
|
||||
}
|
||||
else if (mode == "wdtt-runtime-signature")
|
||||
exit(print_signature_hash(wdtt_runtime_signature_body(uci_sections("section"))) ? 0 : 1);
|
||||
else if (mode == "wdtt-runtime-signature-fixture") {
|
||||
let data = fixture_data(ARGV[1]);
|
||||
exit(print_signature_hash(wdtt_runtime_signature_body(fixture_section_list(data))) ? 0 : 1);
|
||||
}
|
||||
else if (mode == "olcrtc-runtime-signature")
|
||||
exit(print_signature_hash(olcrtc_runtime_signature_body(uci_sections("section"))) ? 0 : 1);
|
||||
else if (mode == "olcrtc-runtime-signature-fixture") {
|
||||
let data = fixture_data(ARGV[1]);
|
||||
exit(print_signature_hash(olcrtc_runtime_signature_body(fixture_section_list(data))) ? 0 : 1);
|
||||
}
|
||||
else if (mode == "dont-touch-dhcp")
|
||||
print(dont_touch_dhcp_value(uci_settings()), "\n");
|
||||
else if (mode == "dont-touch-dhcp-fixture") {
|
||||
|
||||
@@ -1801,6 +1801,8 @@ function view_system_info(token, chat_id, msg_id) {
|
||||
if (info.zapret_installed == "1") text += "🛡 Zapret: <code>" + as_string(info.zapret_version || "?") + "</code>\n";
|
||||
if (info.zapret2_installed == "1") text += "🛡 Zapret2: <code>" + as_string(info.zapret2_version || "?") + "</code>\n";
|
||||
if (info.byedpi_installed == "1") text += "🛡 ByeDPI: <code>" + as_string(info.byedpi_version || "?") + "</code>\n";
|
||||
if (info.wdtt_installed == "1") text += "🛡 WDTT: <code>" + as_string(info.wdtt_version || "?") + "</code>\n";
|
||||
if (info.olcrtc_installed == "1") text += "🛡 OlcRTC: <code>" + as_string(info.olcrtc_version || "?") + "</code>\n";
|
||||
} catch(e) {
|
||||
text += t("info_read_error") + " " + e;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
let fs = require("fs");
|
||||
let common = require("core.common");
|
||||
let uci_core = require("core.uci");
|
||||
let connections = require("config.connections");
|
||||
|
||||
let as_string = common.as_string;
|
||||
|
||||
@@ -1485,6 +1486,65 @@ function action_dir(kind) {
|
||||
return "";
|
||||
}
|
||||
|
||||
function latency_boot_sweep() {
|
||||
let guard_file = "/var/run/tachyon/boot-sweep-done";
|
||||
if (fs.stat(guard_file) != null)
|
||||
return;
|
||||
|
||||
ensure_dirs();
|
||||
|
||||
let sections = uci.sections(CONFIG_NAME, "section");
|
||||
let has_groups = false;
|
||||
for (let section in sections) {
|
||||
if (!bool_option(section, "enabled", true))
|
||||
continue;
|
||||
if (!bool_option(section, "urltest_enabled", false))
|
||||
continue;
|
||||
has_groups = true;
|
||||
break;
|
||||
}
|
||||
if (!has_groups) {
|
||||
fs.writefile(guard_file, as_string(time()));
|
||||
return;
|
||||
}
|
||||
|
||||
let clash_api_check = command_status("curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:9090/ 2>/dev/null | grep -q '200\\|404'") == 0;
|
||||
if (!clash_api_check) {
|
||||
fs.writefile(guard_file, as_string(time()));
|
||||
return;
|
||||
}
|
||||
|
||||
for (let section in sections) {
|
||||
if (!bool_option(section, "enabled", true))
|
||||
continue;
|
||||
if (!bool_option(section, "urltest_enabled", false))
|
||||
continue;
|
||||
|
||||
let section_name = section[".name"];
|
||||
let urltest_id = "";
|
||||
for (let ut in connections.urltests(section)) {
|
||||
urltest_id = ut;
|
||||
break;
|
||||
}
|
||||
if (urltest_id == "")
|
||||
continue;
|
||||
|
||||
let group_tag = section_name + "-urltest-" + urltest_id;
|
||||
let id = "boot-sweep-" + section_name;
|
||||
let path = job_state_path_value(LATENCY_ACTION_DIR, id);
|
||||
if (path == "")
|
||||
continue;
|
||||
|
||||
write_state_file(path, running_latency_action_value("group", section_name, group_tag, now_seconds()));
|
||||
let method_plan = latency_clash_method("group");
|
||||
command_status(command_from_args([ BIN_PATH, "clash_api", "get_group_latency", group_tag, method_plan.timeout, path ]) + " >/dev/null 2>&1");
|
||||
if (fs.stat(path) != null)
|
||||
write_finished_action_state(path, true, "Boot latency sweep completed", 0);
|
||||
}
|
||||
|
||||
fs.writefile(guard_file, as_string(time()));
|
||||
}
|
||||
|
||||
function action_ack(kind, job_id_value) {
|
||||
let dir = action_dir(kind);
|
||||
if (dir == "") {
|
||||
@@ -1594,6 +1654,8 @@ else if (mode == "latency-test-status")
|
||||
latency_test_status(ARGV[1]);
|
||||
else if (mode == "action-ack")
|
||||
action_ack(ARGV[1], ARGV[2]);
|
||||
else if (mode == "latency-boot-sweep")
|
||||
latency_boot_sweep();
|
||||
else if (mode == "cleanup-action-dir-fixture")
|
||||
cleanup_dir(ARGV[1]);
|
||||
else {
|
||||
|
||||
@@ -619,7 +619,8 @@ function base_config(settings, service_address, runtime_context) {
|
||||
|
||||
function mixed_proxy_enabled_action(action) {
|
||||
return action == "connection" || action == "proxy" || action == "outbound" || action == "vpn" ||
|
||||
action == "awg" || action == "byedpi" || action == "zapret" || action == "zapret2";
|
||||
action == "awg" || action == "byedpi" || action == "zapret" || action == "zapret2" ||
|
||||
action == "wdtt" || action == "olcrtc";
|
||||
}
|
||||
|
||||
function add_mixed_proxy_for_section(config, section, service_address) {
|
||||
|
||||
@@ -1802,6 +1802,32 @@ function add_byedpi_outbound(config, section, sections) {
|
||||
});
|
||||
}
|
||||
|
||||
function add_wdtt_outbound(config, section, sections) {
|
||||
let index = enabled_action_index(sections, section, "wdtt");
|
||||
if (index <= 0)
|
||||
ctx.runtime_generate_unsupported("unable to resolve WDTT index for " + section[".name"]);
|
||||
push(config.outbounds, {
|
||||
type: "socks",
|
||||
tag: outbound_tag(section[".name"]),
|
||||
server: runtime_constants.WDTT_LISTEN_ADDRESS,
|
||||
server_port: runtime_constants.WDTT_PORT_BASE + index - 1,
|
||||
version: "5"
|
||||
});
|
||||
}
|
||||
|
||||
function add_olcrtc_outbound(config, section, sections) {
|
||||
let index = enabled_action_index(sections, section, "olcrtc");
|
||||
if (index <= 0)
|
||||
ctx.runtime_generate_unsupported("unable to resolve OlcRTC index for " + section[".name"]);
|
||||
push(config.outbounds, {
|
||||
type: "socks",
|
||||
tag: outbound_tag(section[".name"]),
|
||||
server: runtime_constants.OLCRTC_LISTEN_ADDRESS,
|
||||
server_port: runtime_constants.OLCRTC_PORT_BASE + index - 1,
|
||||
version: "5"
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
init,
|
||||
reserved_runtime_tag_set,
|
||||
@@ -1836,5 +1862,7 @@ return {
|
||||
add_openvpn_endpoint,
|
||||
add_zapret_outbound,
|
||||
add_zapret2_outbound,
|
||||
add_byedpi_outbound
|
||||
add_byedpi_outbound,
|
||||
add_wdtt_outbound,
|
||||
add_olcrtc_outbound
|
||||
};
|
||||
|
||||
@@ -936,7 +936,14 @@ function domain_ip_list_ruleset_tag(section_name) {
|
||||
|
||||
function domain_ip_list_ruleset_path(section_name) {
|
||||
let folder = ctx.runtime_ruleset_folder || runtime_ruleset_folder;
|
||||
return folder + "/" + domain_ip_list_ruleset_tag(section_name) + ".json";
|
||||
let tag = domain_ip_list_ruleset_tag(section_name);
|
||||
let tmp_path = folder + "/" + tag + ".json";
|
||||
let etc_path = "/etc/tachyon/rulesets/" + tag + ".json";
|
||||
if (source_rulesets.has_rules(tmp_path))
|
||||
return tmp_path;
|
||||
if (source_rulesets.has_rules(etc_path))
|
||||
return etc_path;
|
||||
return tmp_path;
|
||||
}
|
||||
|
||||
function reference_is_local(reference) {
|
||||
@@ -948,7 +955,8 @@ function source_file_exists(path) {
|
||||
}
|
||||
|
||||
function rebuild_local_domain_ip_list_ruleset(section_name, references, domains_only) {
|
||||
let ruleset_path = domain_ip_list_ruleset_path(section_name);
|
||||
let folder = ctx.runtime_ruleset_folder || runtime_ruleset_folder;
|
||||
let ruleset_path = folder + "/" + domain_ip_list_ruleset_tag(section_name) + ".json";
|
||||
let has_local = false;
|
||||
|
||||
for (let reference in references) {
|
||||
@@ -1086,9 +1094,15 @@ function push_dns_matcher_rule(config, rule) {
|
||||
}
|
||||
|
||||
function section_dns_server(section) {
|
||||
return option(section, "action", "") == "bypass"
|
||||
? runtime_constants.DNS_SERVER_TAG
|
||||
: runtime_constants.FAKEIP_DNS_SERVER_TAG;
|
||||
if (option(section, "action", "") == "bypass")
|
||||
return runtime_constants.DNS_SERVER_TAG;
|
||||
if (connections.routed_dns_enabled(section))
|
||||
return runtime_constants.tag(section[".name"], "routed-dns-server");
|
||||
return runtime_constants.FAKEIP_DNS_SERVER_TAG;
|
||||
}
|
||||
|
||||
function routed_dns_server_tag(section_name) {
|
||||
return runtime_constants.tag(section_name, "routed-dns-server");
|
||||
}
|
||||
|
||||
function single_or_array(values) {
|
||||
@@ -1143,6 +1157,32 @@ function add_dns_server_for_section(config, section) {
|
||||
return server_tags;
|
||||
}
|
||||
|
||||
function add_routed_dns_server_for_section(config, section) {
|
||||
if (!connections.routed_dns_enabled(section))
|
||||
return;
|
||||
let section_name = section[".name"];
|
||||
let servers = connections.routed_dns_servers(section);
|
||||
let dns_type = connections.routed_dns_type(section);
|
||||
let tag_name = routed_dns_server_tag(section_name);
|
||||
let detour = outbound_tag(section_name);
|
||||
|
||||
for (let i = 0; i < length(servers); i++) {
|
||||
let s_val = servers[i];
|
||||
let server_tag = length(servers) <= 1
|
||||
? tag_name
|
||||
: runtime_constants.tag(section_name, "routed-dns-server-" + (i + 1));
|
||||
let server = runtime_dns.server_from_options(
|
||||
server_tag,
|
||||
dns_type,
|
||||
s_val,
|
||||
detour
|
||||
);
|
||||
if (server.unsupported)
|
||||
ctx.runtime_generate_unsupported(server.unsupported);
|
||||
push(config.dns.servers, server);
|
||||
}
|
||||
}
|
||||
|
||||
function source_dns_inbound_matcher() {
|
||||
return [ runtime_constants.SOURCE_DNS_INBOUND_TAG ];
|
||||
}
|
||||
@@ -1369,11 +1409,26 @@ function add_excluded_ips_rule(config, section) {
|
||||
if (length(excluded) == 0)
|
||||
return;
|
||||
|
||||
let resolved = [];
|
||||
for (let item in excluded) {
|
||||
let val = trim(as_string(item));
|
||||
if (val == "") continue;
|
||||
let is_mac = match(val, /^([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$/) != null;
|
||||
if (is_mac) {
|
||||
for (let res_ip in core_ip.resolve_mac_to_ips(val))
|
||||
push(resolved, res_ip + (index(res_ip, ":") != -1 ? "/128" : "/32"));
|
||||
} else {
|
||||
push(resolved, val);
|
||||
}
|
||||
}
|
||||
if (length(resolved) == 0)
|
||||
return;
|
||||
|
||||
let route_rule = {
|
||||
action: "route",
|
||||
inbound: tproxy_inbound_matcher(),
|
||||
outbound: runtime_constants.DIRECT_OUTBOUND_TAG,
|
||||
source_ip_cidr: single_or_array(excluded)
|
||||
source_ip_cidr: single_or_array(resolved)
|
||||
};
|
||||
push(config.route.rules, route_rule);
|
||||
}
|
||||
@@ -1642,6 +1697,10 @@ function add_outbound_for_section(config, section, taken, sections) {
|
||||
ctx.outbounds.add_zapret2_outbound(config, section, sections);
|
||||
else if (action == "byedpi")
|
||||
ctx.outbounds.add_byedpi_outbound(config, section, sections);
|
||||
else if (action == "wdtt")
|
||||
ctx.outbounds.add_wdtt_outbound(config, section, sections);
|
||||
else if (action == "olcrtc")
|
||||
ctx.outbounds.add_olcrtc_outbound(config, section, sections);
|
||||
else if (action == "bypass") {
|
||||
/* route-only action */
|
||||
}
|
||||
@@ -1657,6 +1716,9 @@ function add_outbound_for_section(config, section, taken, sections) {
|
||||
else {
|
||||
ctx.runtime_generate_unsupported("unsupported action " + action);
|
||||
}
|
||||
|
||||
if (action != "dns" && action != "hosts" && action != "bypass" && action != "block")
|
||||
add_routed_dns_server_for_section(config, section);
|
||||
}
|
||||
|
||||
function reserve_section_outbound_tags(sections, taken) {
|
||||
@@ -1664,6 +1726,7 @@ function reserve_section_outbound_tags(sections, taken) {
|
||||
let action = option(section, "action", "");
|
||||
if (connections.is_connections_action(action) ||
|
||||
action == "awg" || action == "warp" || action == "byedpi" || action == "zapret" || action == "zapret2" ||
|
||||
action == "wdtt" || action == "olcrtc" ||
|
||||
action == "anytls" || action == "snell" || action == "mieru" || action == "sudoku" ||
|
||||
action == "masque" || action == "openvpn")
|
||||
taken[outbound_tag(section[".name"])] = true;
|
||||
@@ -1695,6 +1758,7 @@ function failover_candidate(sections) {
|
||||
let action = option(section, "action", "");
|
||||
if (connections.is_connections_action(action) ||
|
||||
action == "awg" || action == "warp" || action == "byedpi" || action == "zapret" || action == "zapret2" ||
|
||||
action == "wdtt" || action == "olcrtc" ||
|
||||
action == "anytls" || action == "snell" || action == "mieru" || action == "sudoku" ||
|
||||
action == "masque" || action == "openvpn") {
|
||||
push(result, section);
|
||||
|
||||
@@ -72,13 +72,16 @@ function config(settings, runtime) {
|
||||
if (type(runtime) == "object" && type(runtime.dns_health_inbounds) == "array")
|
||||
for (let inbound in runtime.dns_health_inbounds)
|
||||
push(sniff_inbounds, inbound);
|
||||
let dns_hijack_inbounds = [ runtime_constants.DNS_INBOUND_TAG ];
|
||||
if (type(runtime) == "object" && bool_value(runtime.source_aware_dns))
|
||||
push(dns_hijack_inbounds, runtime_constants.SOURCE_DNS_INBOUND_TAG);
|
||||
if (type(runtime) == "object" && type(runtime.dns_health_inbounds) == "array")
|
||||
for (let inbound in runtime.dns_health_inbounds)
|
||||
push(dns_hijack_inbounds, inbound);
|
||||
let rules = [
|
||||
{ action: "sniff", inbound: sniff_inbounds },
|
||||
{ action: "hijack-dns", port: 53 },
|
||||
{ action: "hijack-dns", protocol: "dns" }
|
||||
{ action: "hijack-dns", inbound: dns_hijack_inbounds }
|
||||
];
|
||||
if (type(runtime) == "object" && bool_value(runtime.source_aware_dns))
|
||||
push(rules, { action: "hijack-dns", inbound: [ runtime_constants.SOURCE_DNS_INBOUND_TAG ] });
|
||||
let result = {
|
||||
rules,
|
||||
rule_set: [],
|
||||
@@ -120,7 +123,8 @@ function target(section, outbound_tag_name) {
|
||||
action == "awg" || action == "warp" ||
|
||||
action == "anytls" || action == "snell" || action == "mieru" || action == "sudoku" ||
|
||||
action == "masque" || action == "openvpn" ||
|
||||
action == "byedpi" || action == "zapret" || action == "zapret2")
|
||||
action == "byedpi" || action == "zapret" || action == "zapret2" ||
|
||||
action == "wdtt" || action == "olcrtc")
|
||||
return { action: "route", outbound: outbound_tag_name };
|
||||
if (action == "bypass")
|
||||
return { action: "route", outbound: runtime_constants.BYPASS_OUTBOUND_TAG };
|
||||
@@ -139,6 +143,7 @@ function has_resolve_matchers(rule) {
|
||||
function resolve_rule_for_section(section, route_rule) {
|
||||
let action = option(section, "action", "");
|
||||
let should_resolve = action == "byedpi" || action == "zapret" || action == "zapret2" ||
|
||||
action == "wdtt" || action == "olcrtc" ||
|
||||
(connections.is_connections_action(action) &&
|
||||
bool_option(section, "resolve_real_ip_for_routing", false));
|
||||
|
||||
|
||||
@@ -703,6 +703,12 @@ bypass
|
||||
|
||||
[rule.text_rule.excluded_protocol]
|
||||
|
||||
[rule.text_rule.routed_dns_enabled]
|
||||
0
|
||||
[rule.text_rule.routed_dns_type]
|
||||
|
||||
[rule.text_rule.routed_dns_server]
|
||||
|
||||
[rule.text_rule.protocol]
|
||||
|
||||
[rule.text_rule.community_subnet_lists]
|
||||
@@ -731,6 +737,12 @@ proxy
|
||||
|
||||
[rule.default_enabled.excluded_protocol]
|
||||
|
||||
[rule.default_enabled.routed_dns_enabled]
|
||||
0
|
||||
[rule.default_enabled.routed_dns_type]
|
||||
|
||||
[rule.default_enabled.routed_dns_server]
|
||||
|
||||
[rule.default_enabled.protocol]
|
||||
|
||||
[rule.default_enabled.community_subnet_lists]
|
||||
@@ -799,6 +811,12 @@ bypass
|
||||
|
||||
[rule.enabled.excluded_protocol]
|
||||
|
||||
[rule.enabled.routed_dns_enabled]
|
||||
0
|
||||
[rule.enabled.routed_dns_type]
|
||||
|
||||
[rule.enabled.routed_dns_server]
|
||||
|
||||
[rule.enabled.protocol]
|
||||
|
||||
[rule.enabled.community_subnet_lists]
|
||||
|
||||
@@ -1451,6 +1451,8 @@ zapret_runtime_signature=$(state_ucode zapret-runtime-signature-fixture "$WORK_D
|
||||
zapret2_queue_signature=$(state_ucode zapret2-queue-signature-fixture "$WORK_DIR/reload-state-signatures.json")
|
||||
zapret2_runtime_signature=$(state_ucode zapret2-runtime-signature-fixture "$WORK_DIR/reload-state-signatures.json")
|
||||
byedpi_runtime_signature=$(state_ucode byedpi-runtime-signature-fixture "$WORK_DIR/reload-state-signatures.json")
|
||||
wdtt_runtime_signature=$(state_ucode wdtt-runtime-signature-fixture "$WORK_DIR/reload-state-signatures.json")
|
||||
olcrtc_runtime_signature=$(state_ucode olcrtc-runtime-signature-fixture "$WORK_DIR/reload-state-signatures.json")
|
||||
list_signature=$(state_ucode list-update-signature-fixture "$WORK_DIR/reload-state-signatures.json")
|
||||
hosts_list_signature=$(state_ucode hosts-list-signature-fixture "$WORK_DIR/reload-state-signatures.json")
|
||||
cron_signature=$(state_ucode cron-signature-fixture "$WORK_DIR/reload-state-signatures.json")
|
||||
|
||||
Reference in New Issue
Block a user