Files
NeoTachyon/tachyon/files/usr/lib/providers/wdtt/validator.uc
T
Dushnilin 26431f50d3 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
2026-09-11 18:04:03 +03:00

240 lines
6.5 KiB
Ucode

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
};