generated from Dicken/dickendock
round46: Prozessweite Verbindungs-Obergrenze statt zwei sich multiplizierender Parallelitaets-Werte (behebt verlorene Ports wie FritzBox 443 bei mehreren gleichzeitigen Geraete-Scans)
This commit is contained in:
@@ -17,32 +17,82 @@ export const TYPICAL_PORTS = [
|
|||||||
8181, 8282,
|
8181, 8282,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Einfacher Zähl-Semaphore, um die GESAMTZAHL gleichzeitig offener Sockets
|
||||||
|
* über den kompletten Backend-Prozess hinweg zu begrenzen - nicht nur pro
|
||||||
|
* einzelnem Geräte-Scan. Wichtig, seit mehrere Geräte gleichzeitig gescannt
|
||||||
|
* werden (siehe routes/scan.ts, CONCURRENT_DEVICES): ohne einen
|
||||||
|
* PROZESSWEITEN Deckel hätten sich Geräte-Parallelität (bis zu 10) und
|
||||||
|
* Port-Batch-Größe (bis zu 1000) multipliziert - bis zu 10.000 gleichzeitige
|
||||||
|
* Verbindungsversuche, die den Container/das Heimnetz überfordern und dabei
|
||||||
|
* auch eigentlich funktionierende Ports (wie 443 der FritzBox selbst)
|
||||||
|
* fehlschlagen lassen konnten.
|
||||||
|
*/
|
||||||
|
class Semaphore {
|
||||||
|
private available: number;
|
||||||
|
private readonly queue: (() => void)[] = [];
|
||||||
|
|
||||||
|
constructor(count: number) {
|
||||||
|
this.available = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
acquire(): Promise<void> {
|
||||||
|
if (this.available > 0) {
|
||||||
|
this.available--;
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return new Promise((resolve) => this.queue.push(resolve));
|
||||||
|
}
|
||||||
|
|
||||||
|
release(): void {
|
||||||
|
const next = this.queue.shift();
|
||||||
|
if (next) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
this.available++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prozessweiter Deckel für gleichzeitig offene Scan-Sockets - unabhängig
|
||||||
|
// davon, ob das gerade ein einzelner Geräte-Scan oder mehrere parallele
|
||||||
|
// Geräte-Scans sind (siehe Kommentar oben).
|
||||||
|
const globalScanSemaphore = new Semaphore(300);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prüft per TCP-Connect, ob ein Port offen ist. Kein Protokoll-Handshake,
|
* Prüft per TCP-Connect, ob ein Port offen ist. Kein Protokoll-Handshake,
|
||||||
* nur "kann eine Verbindung aufgebaut werden" – schnell und protokollunabhängig.
|
* nur "kann eine Verbindung aufgebaut werden" – schnell und protokollunabhängig.
|
||||||
|
* Wartet ggf. kurz auf ein freies "Ticket" im prozessweiten Semaphore (siehe
|
||||||
|
* oben), bevor der eigentliche Verbindungsversuch losgeht.
|
||||||
*/
|
*/
|
||||||
export function isPortOpen(host: string, port: number, timeoutMs = 800): Promise<boolean> {
|
export async function isPortOpen(host: string, port: number, timeoutMs = 800): Promise<boolean> {
|
||||||
return new Promise((resolve) => {
|
await globalScanSemaphore.acquire();
|
||||||
const socket = connect({ host, port, timeout: timeoutMs });
|
try {
|
||||||
|
return await new Promise((resolve) => {
|
||||||
|
const socket = connect({ host, port, timeout: timeoutMs });
|
||||||
|
|
||||||
const finish = (result: boolean) => {
|
const finish = (result: boolean) => {
|
||||||
socket.removeAllListeners();
|
socket.removeAllListeners();
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
resolve(result);
|
resolve(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.once("connect", () => finish(true));
|
socket.once("connect", () => finish(true));
|
||||||
socket.once("timeout", () => finish(false));
|
socket.once("timeout", () => finish(false));
|
||||||
socket.once("error", () => finish(false));
|
socket.once("error", () => finish(false));
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
globalScanSemaphore.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prüft eine (ggf. sehr lange) Liste von Ports in Batches mit begrenzter
|
* Prüft eine (ggf. sehr lange) Liste von Ports - für den vollständigen
|
||||||
* Parallelität - für den vollständigen Portscan (1-65535, siehe
|
* Portscan (1-65535, siehe scanDeviceServices mit fullScan=true). Alle
|
||||||
* scanDeviceServices mit fullScan=true). Alle Ports auf einmal parallel zu
|
* Verbindungsversuche werden sofort gestartet, die eigentliche Begrenzung
|
||||||
* prüfen wäre bei 65535 Ports zu viel gleichzeitig offener Sockets; komplett
|
* der Gleichzeitigkeit übernimmt der prozessweite Semaphore in isPortOpen
|
||||||
* nacheinander wäre selbst bei schnellen RSTs zu langsam.
|
* (siehe oben) - dadurch bleibt die Gesamtzahl gleichzeitiger Sockets sicher
|
||||||
|
* gedeckelt, auch wenn mehrere Geräte gleichzeitig gescannt werden.
|
||||||
*
|
*
|
||||||
* Timeout bewusst knapp, aber nicht zu knapp (500ms): im lokalen Netz
|
* Timeout bewusst knapp, aber nicht zu knapp (500ms): im lokalen Netz
|
||||||
* (derselbe Subnetz-Bereich) liegt die Round-Trip-Zeit normalerweise im
|
* (derselbe Subnetz-Bereich) liegt die Round-Trip-Zeit normalerweise im
|
||||||
@@ -55,18 +105,12 @@ export function isPortOpen(host: string, port: number, timeoutMs = 800): Promise
|
|||||||
export async function scanPortsInBatches(
|
export async function scanPortsInBatches(
|
||||||
host: string,
|
host: string,
|
||||||
ports: number[],
|
ports: number[],
|
||||||
batchSize = 1000,
|
|
||||||
timeoutMs = 500
|
timeoutMs = 500
|
||||||
): Promise<number[]> {
|
): Promise<number[]> {
|
||||||
const open: number[] = [];
|
const results = await Promise.all(
|
||||||
for (let i = 0; i < ports.length; i += batchSize) {
|
ports.map(async (port) => ({ port, open: await isPortOpen(host, port, timeoutMs) }))
|
||||||
const batch = ports.slice(i, i + batchSize);
|
);
|
||||||
const results = await Promise.all(
|
return results.filter((r) => r.open).map((r) => r.port);
|
||||||
batch.map(async (port) => ({ port, open: await isPortOpen(host, port, timeoutMs) }))
|
|
||||||
);
|
|
||||||
for (const r of results) if (r.open) open.push(r.port);
|
|
||||||
}
|
|
||||||
return open;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Alle Ports von 1 bis 65535 - für den vollständigen (statt nur "typische Ports") Scan eines einzelnen Geräts. */
|
/** Alle Ports von 1 bis 65535 - für den vollständigen (statt nur "typische Ports") Scan eines einzelnen Geräts. */
|
||||||
|
|||||||
Reference in New Issue
Block a user