generated from Dicken/dickendock
round44: Optionale masscan-Beschleunigung fuer den vollstaendigen Portscan mit automatischem Fallback
This commit is contained in:
@@ -35,7 +35,11 @@ ENV HOST=0.0.0.0
|
||||
# iputils-ping stellt einen "echten" ping-Befehl mit ICMP-Unterstützung
|
||||
# bereit (nicht nur die BusyBox-Variante) - für den optionalen Live-Status-
|
||||
# Heartbeat (siehe src/liveStatus.ts, standardmäßig deaktiviert).
|
||||
RUN apk add --no-cache iputils-ping
|
||||
# masscan beschleunigt den vollständigen Portscan (1-65535, siehe
|
||||
# scanner/masscan.ts) erheblich gegenüber reinem TCP-Connect-Scanning - wird
|
||||
# automatisch genutzt, falls vorhanden, sonst fällt der Scanner auf die
|
||||
# eingebaute (langsamere, aber ohne Zusatzrechte auskommende) Methode zurück.
|
||||
RUN apk add --no-cache iputils-ping masscan
|
||||
|
||||
COPY --from=build /app/deploy/ ./
|
||||
COPY --from=build /app/apps/backend/dist ./dist
|
||||
|
||||
68
apps/backend/src/scanner/masscan.ts
Normal file
68
apps/backend/src/scanner/masscan.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { execFile } from "node:child_process";
|
||||
|
||||
let masscanConfirmedUnavailable = false;
|
||||
|
||||
/**
|
||||
* Nutzt masscan (falls im Container installiert, siehe Dockerfile) für den
|
||||
* vollständigen Portscan (1-65535) - masscan verschickt SYN-Pakete
|
||||
* asynchron per Rohsocket, ohne auf jede einzelne TCP-Verbindung zu warten,
|
||||
* und ist dadurch um ein Vielfaches schneller als das eingebaute
|
||||
* Batch-TCP-Connect-Scanning (scanPortsInBatches in ports.ts).
|
||||
*
|
||||
* Fällt automatisch (und leise) auf null zurück, wenn masscan fehlt, keine
|
||||
* Rechte hat (braucht CAP_NET_RAW, siehe docker-compose.yml) oder aus
|
||||
* anderem Grund fehlschlägt - der Aufrufer nutzt dann stattdessen
|
||||
* scanPortsInBatches. Kein hartes Erfordernis, nur eine Beschleunigung.
|
||||
*/
|
||||
export async function scanWithMasscan(
|
||||
host: string,
|
||||
portRange: string,
|
||||
rate = 2000
|
||||
): Promise<number[] | null> {
|
||||
if (masscanConfirmedUnavailable) return null;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
execFile(
|
||||
"masscan",
|
||||
[host, "-p", portRange, "--rate", String(rate), "-oL", "-"],
|
||||
{ timeout: 60_000 },
|
||||
(error, stdout, stderr) => {
|
||||
if (error) {
|
||||
// ENOENT (Programm fehlt) oder fehlende Rechte (EPERM/"PCAP" o. ä.
|
||||
// in stderr) - beides bedeutet: masscan für diese Installation
|
||||
// dauerhaft nicht nutzbar, künftig direkt auf den Fallback gehen
|
||||
// statt bei jedem Scan erneut den (dann meist schnell fehlschlagenden)
|
||||
// Versuch zu machen.
|
||||
const msg = `${error.message} ${stderr ?? ""}`.toLowerCase();
|
||||
if (
|
||||
msg.includes("enoent") ||
|
||||
msg.includes("not found") ||
|
||||
msg.includes("permission") ||
|
||||
msg.includes("operation not permitted") ||
|
||||
msg.includes("pcap")
|
||||
) {
|
||||
masscanConfirmedUnavailable = true;
|
||||
}
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// "-oL -" (List-Format) gibt pro offenem Port eine Zeile aus:
|
||||
// "open tcp 80 192.168.1.50 1700000000"
|
||||
const ports: number[] = [];
|
||||
for (const line of stdout.split("\n")) {
|
||||
const parts = line.trim().split(/\s+/);
|
||||
if (parts[0] === "open" && parts[1] === "tcp") {
|
||||
const port = Number(parts[2]);
|
||||
if (Number.isFinite(port)) ports.push(port);
|
||||
}
|
||||
}
|
||||
resolve(ports);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function isMasscanConfirmedUnavailable(): boolean {
|
||||
return masscanConfirmedUnavailable;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { resolveHostname, reverseLookup } from "./dns.js";
|
||||
import { isPortOpen, scanPortsInBatches, fullPortRange, TYPICAL_PORTS } from "./ports.js";
|
||||
import { scanWithMasscan } from "./masscan.js";
|
||||
import { probeHttp } from "./http.js";
|
||||
import { detectSoftware } from "./softwareDetection.js";
|
||||
import { findBestIconMatch } from "./iconDb.js";
|
||||
@@ -106,7 +107,18 @@ export async function scanDeviceServices(
|
||||
// (~30 Ports) macht das kaum einen Unterschied, ist aber zwingend nötig für
|
||||
// den optionalen VOLLSTÄNDIGEN Portscan (1-65535, siehe Admin -> Geräte),
|
||||
// wo alle Ports gleichzeitig öffnen würde.
|
||||
let openPorts = await scanPortsInBatches(device.ip, candidatePorts);
|
||||
// Bei einer großen Portliste (voller Scan, 1-65535) lohnt sich masscan,
|
||||
// falls installiert (siehe scanner/masscan.ts + Dockerfile) - deutlich
|
||||
// schneller als das eingebaute Batch-Scanning. Bei der kurzen "üblichen
|
||||
// Ports"-Liste (~30 Ports) macht masscan kaum einen Unterschied, das
|
||||
// eingebaute Scanning reicht dafür völlig.
|
||||
let openPorts: number[];
|
||||
if (candidatePorts.length > 1000) {
|
||||
const masscanResult = await scanWithMasscan(device.ip, "1-65535");
|
||||
openPorts = masscanResult ?? (await scanPortsInBatches(device.ip, candidatePorts));
|
||||
} else {
|
||||
openPorts = await scanPortsInBatches(device.ip, candidatePorts);
|
||||
}
|
||||
|
||||
// Bereits bekannte Dienst-Ports (priorityPorts, vom Aufrufer übergeben -
|
||||
// siehe scan.ts), die im ersten, stark parallelen Durchlauf NICHT als
|
||||
|
||||
Reference in New Issue
Block a user