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
|
# iputils-ping stellt einen "echten" ping-Befehl mit ICMP-Unterstützung
|
||||||
# bereit (nicht nur die BusyBox-Variante) - für den optionalen Live-Status-
|
# bereit (nicht nur die BusyBox-Variante) - für den optionalen Live-Status-
|
||||||
# Heartbeat (siehe src/liveStatus.ts, standardmäßig deaktiviert).
|
# 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/deploy/ ./
|
||||||
COPY --from=build /app/apps/backend/dist ./dist
|
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 { resolveHostname, reverseLookup } from "./dns.js";
|
||||||
import { isPortOpen, scanPortsInBatches, fullPortRange, TYPICAL_PORTS } from "./ports.js";
|
import { isPortOpen, scanPortsInBatches, fullPortRange, TYPICAL_PORTS } from "./ports.js";
|
||||||
|
import { scanWithMasscan } from "./masscan.js";
|
||||||
import { probeHttp } from "./http.js";
|
import { probeHttp } from "./http.js";
|
||||||
import { detectSoftware } from "./softwareDetection.js";
|
import { detectSoftware } from "./softwareDetection.js";
|
||||||
import { findBestIconMatch } from "./iconDb.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
|
// (~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),
|
// den optionalen VOLLSTÄNDIGEN Portscan (1-65535, siehe Admin -> Geräte),
|
||||||
// wo alle Ports gleichzeitig öffnen würde.
|
// 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 -
|
// Bereits bekannte Dienst-Ports (priorityPorts, vom Aufrufer übergeben -
|
||||||
// siehe scan.ts), die im ersten, stark parallelen Durchlauf NICHT als
|
// siehe scan.ts), die im ersten, stark parallelen Durchlauf NICHT als
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ services:
|
|||||||
- ./apps/backend/plugins:/app/plugins:ro
|
- ./apps/backend/plugins:/app/plugins:ro
|
||||||
ports:
|
ports:
|
||||||
- "3001:3001"
|
- "3001:3001"
|
||||||
|
# Für den optionalen, per masscan beschleunigten vollständigen Portscan
|
||||||
|
# (siehe scanner/masscan.ts) - masscan braucht Zugriff auf rohe Sockets.
|
||||||
|
# Ohne diese Capabilities funktioniert LaunchPad trotzdem ganz normal,
|
||||||
|
# der Portscan fällt dann automatisch auf die eingebaute (langsamere)
|
||||||
|
# Methode zurück - kein Muss, aber empfohlen für Tempo.
|
||||||
|
cap_add:
|
||||||
|
- NET_RAW
|
||||||
|
- NET_ADMIN
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
build:
|
build:
|
||||||
|
|||||||
Reference in New Issue
Block a user