Vydané zdroje
Stáhnout zdroje ZIP
CH-J Server Manager
Procházejte adresáře a soubory konkrétního vydání aplikace.
Zdroje jsou zveřejněny pod CH-J Proprietary Software License 1.14. Jejich dostupnost nemění licenční podmínky ani neposkytuje další oprávnění.
1
"use strict";2
const { spawn } = require("node:child_process");3
const net = require("node:net");4
const { setTimeout: delay } = require("node:timers/promises");5
const { DiagnosticError, checkAbort, stats, now } = require("./common");6
const { parsePing } = require("../sessions/latencyMonitor");7
function runProcess(executable, args, { timeoutMs = 5000, signal, onLine, maxBytes = 256 * 1024, spawnImpl = spawn } = {}) {8
checkAbort(signal);9
return new Promise((resolve, reject) => {10
const child = spawnImpl(executable, args, { shell: false, windowsHide: true, env: { ...process.env, LC_ALL: "C", LANG: "C" }, stdio: ["ignore", "pipe", "pipe"] });11
let stdout = [], stderr = [], bytes = 0, pending = "", failure, settled = false, killTimer;12
const terminate = (error) => { failure ||= error; child.kill(); killTimer ||= setTimeout(() => child.kill("SIGKILL"), 1000); killTimer.unref?.(); };13
const abort = () => terminate(new DiagnosticError("CANCELLED"));14
const timer = setTimeout(() => terminate(new DiagnosticError("TIMEOUT")), timeoutMs);15
signal?.addEventListener("abort", abort, { once: true });16
const finish = (error, code) => { if (settled) return; settled = true; clearTimeout(timer); clearTimeout(killTimer); signal?.removeEventListener("abort", abort); if (pending && onLine) onLine(pending); error || failure ? reject(error || failure) : resolve({ code, stdout: Buffer.concat(stdout), stderr: Buffer.concat(stderr).toString("utf8") }); };17
child.once("error", (error) => finish(error));18
child.stdout.on("data", (chunk) => { bytes += chunk.length; if (bytes > maxBytes) return terminate(new DiagnosticError("OUTPUT_LIMIT")); stdout.push(chunk); if (onLine) { pending += chunk.toString("utf8"); const lines = pending.split(/\r?\n/); pending = lines.pop(); lines.forEach(onLine); } });19
child.stderr.on("data", (chunk) => { bytes += chunk.length; if (bytes > maxBytes) return terminate(new DiagnosticError("OUTPUT_LIMIT")); stderr.push(chunk); });20
child.once("close", (code) => finish(null, code));21
});22
}23
function pingCommand(address, timeoutMs, platform = process.platform) {24
if (!net.isIP(address)) throw new DiagnosticError("INVALID_IP");25
const v6 = net.isIP(address) === 6;26
if (platform === "win32") return { executable: "ping.exe", args: [v6 ? "-6" : "-4", "-n", "1", "-w", String(timeoutMs), address] };27
if (platform === "darwin") return { executable: v6 ? "/sbin/ping6" : "/sbin/ping", args: ["-n", "-c", "1", ...(v6 ? [] : ["-W", String(timeoutMs)]), address] };28
if (platform === "linux") return { executable: "ping", args: [v6 ? "-6" : "-4", "-n", "-c", "1", "-W", String(Math.ceil(timeoutMs / 1000)), address] };29
throw new DiagnosticError("PLATFORM_UNSUPPORTED");30
}31
function parsePingReply(output) {32
const rttMs = parsePing(output), upper = String(output).match(/<\s*(\d+(?:[.,]\d+)?)\s*ms/i), ttl = String(output).match(/(?:ttl|hlim|hop limit)[=:\s]+(\d+)/i);33
return { received: rttMs !== null || Boolean(upper), rttMs, upperBoundMs: upper ? Number(upper[1].replace(",", ".")) : null, ttl: ttl ? Number(ttl[1]) : null };34
}35
async function ping(address, options, signal, progress, runner = runProcess) {36
const packets = []; let sent = 0, received = 0, cycle = 0;37
do {38
for (let index = 0; index < options.count; index++) {39
checkAbort(signal); sent++; let packet;40
try {41
const command = pingCommand(address, options.timeoutMs);42
const result = await runner(command.executable, command.args, { timeoutMs: options.timeoutMs + 500, signal });43
const raw = result.stdout.toString("utf8"); const reply = parsePingReply(raw);44
const explicitTimeout = /Request timed out|Request timeout|Zeitüberschreitung|Vypršel.*časov[ýy].*limit/i.test(raw);45
packet = { sequence: sent, address, ...reply, status: reply.received ? "success" : explicitTimeout ? "timeout" : "no-reply", raw: (raw + result.stderr).slice(0, 4096) };46
if (!reply.received && (result.code > (process.platform === "darwin" ? 2 : 1) || /not permitted|permission denied|invalid option|unknown option|network is unreachable|no route to host|general failure|transmit failed/i.test(result.stderr))) packet.status = "error";47
} catch (error) {48
if (signal.aborted) throw error;49
packet = { sequence: sent, address, received: false, rttMs: null, status: error.code === "ENOENT" ? "unavailable" : error.code === "TIMEOUT" ? "timeout" : "error", code: error.code, raw: String(error.message).slice(0, 400) };50
}51
packet.measuredAt = new Date().toISOString();52
if (["unavailable", "error"].includes(packet.status)) sent--;53
if (packet.received) received++;54
packets.push(packet); if (packets.length > 500) packets.shift();55
progress({ kind: "ping-packet", ...packet, sent, received, lossPercent: sent ? 100 * (sent - received) / sent : null, statistics: stats(packets.map((p) => p.rttMs)) });56
if (["unavailable", "error"].includes(packet.status)) return { status: packet.status, code: packet.code || "PING_TOOL_ERROR", reason: packet.raw, address, packets, sent, received, lossPercent: sent ? 100 * (sent - received) / sent : null, statistics: stats(packets.map((p) => p.rttMs)), note: "ICMP failure does not establish server unavailability or prove a firewall." };57
if (index + 1 < options.count) await delay(1000, undefined, { signal });58
}59
cycle++;60
if (options.continuous || cycle < options.repetitions) await delay(1000, undefined, { signal });61
} while (options.continuous || cycle < options.repetitions);62
return { status: received ? "success" : "warning", address, sent, received, lossPercent: sent ? 100 * (sent - received) / sent : null, packets, statistics: stats(packets.map((p) => p.rttMs)), note: "No ICMP reply may mean filtering or loss; it does not establish server unavailability." };63
}64
function tracerouteCommand(address, options, platform = process.platform) {65
const v6 = net.isIP(address) === 6;66
if (!net.isIP(address)) throw new DiagnosticError("INVALID_IP");67
if (platform === "win32") return { executable: "tracert.exe", args: [v6 ? "-6" : "-4", "-d", "-h", String(options.maxHops), "-w", String(options.timeoutMs), address] };68
if (platform === "darwin") return { executable: v6 ? "/usr/sbin/traceroute6" : "/usr/sbin/traceroute", args: ["-n", "-m", String(options.maxHops), "-w", String(Math.ceil(options.timeoutMs / 1000)), address] };69
if (platform === "linux") return { executable: "traceroute", args: [v6 ? "-6" : "-4", "-n", "-m", String(options.maxHops), "-w", String(options.timeoutMs / 1000), address] };70
throw new DiagnosticError("PLATFORM_UNSUPPORTED");71
}72
function parseHop(line) {73
const match = String(line).match(/^\s*(\d{1,2})\s+(.+)/); if (!match) return null;74
const tokens = match[2].split(/\s+/), addresses = tokens.map((token) => token.replace(/[()[\],]/g, "")).filter((value) => net.isIP(value));75
const probes = [...match[2].matchAll(/(<\s*)?(\d+(?:[.,]\d+)?)\s*ms|\*/gi)].map((m) => m[0] === "*" ? { status: "timeout", rttMs: null } : { status: "success", rttMs: m[1] ? null : Number(m[2].replace(",", ".")), upperBoundMs: m[1] ? Number(m[2]) : null });76
if (!probes.length && !addresses.length) return null;77
return { hop: Number(match[1]), addresses: [...new Set(addresses)], probes, averageMs: stats(probes.map((p) => p.rttMs)).average, lossPercent: probes.length ? 100 * probes.filter((p) => p.status === "timeout").length / probes.length : null, raw: line.slice(0, 1024) };78
}79
async function traceroute(address, options, signal, progress, runner = runProcess) {80
const routes = [];81
for (let iteration = 0; iteration < options.repetitions; iteration++) {82
checkAbort(signal); const hops = [], command = tracerouteCommand(address, options);83
try {84
const result = await runner(command.executable, command.args, { signal, timeoutMs: Math.min(180000, options.maxHops * Math.ceil(options.timeoutMs / 1000) * 1000 * 3 + 2000), onLine(line) { const hop = parseHop(line); if (hop) { hops.push(hop); progress({ kind: "trace-hop", iteration, address, ...hop }); } } });85
routes.push({ iteration, hops, exitCode: result.code, raw: result.stdout.toString("utf8").slice(0, 32000), reason: result.stderr.slice(0, 1000) });86
} catch (error) { if (signal.aborted) throw error; if (hops.length) routes.push({ iteration, hops, incomplete: true, reason: error.message }); return { status: error.code === "ENOENT" ? "unavailable" : "error", code: error.code, reason: error.message, address, routes }; }87
}88
return { status: routes.some((route) => route.hops.some((hop) => hop.addresses.some((ip) => ip === address || net.isIP(ip) === 6 && net.isIP(address) === 6 && new URL(`http://[${ip}]/`).hostname === new URL(`http://[${address}]/`).hostname))) ? "success" : routes.every((route) => route.exitCode !== 0 && !route.hops.length) ? "error" : "warning", address, routes, effectiveProbeTimeoutMs: process.platform === "darwin" ? Math.ceil(options.timeoutMs / 1000) * 1000 : options.timeoutMs, routeChanged: routes.length > 1 ? routes.some((route) => JSON.stringify(route.hops.map((h) => h.addresses)) !== JSON.stringify(routes[0].hops.map((h) => h.addresses))) : null, method: process.platform === "win32" ? "ICMP tracert" : "System UDP traceroute", note: "Intermediate timeouts do not prove a broken route. Loss is the fraction of unanswered displayed probes, not end-to-end packet loss." };89
}90
module.exports = { runProcess, pingCommand, parsePingReply, ping, tracerouteCommand, parseHop, traceroute };SHA-256: 73c6240be94fb54609d1d0cbc2a2a257b5e7bd6edeb878eba2eea8cfd240d6af
SHA-256 archivu: 5ac91caf4fa32a6fdb114f2430deed486fbe7489d5eea343d1f034169fafb5e0