Quelltext des aktuellen Standes
<?php
declare(strict_types=1);
use OpenNIT\Controllers\Admin\IpamScanAgentController;
/**
* Deckt die beiden Scan-Agenten ab, die das IPAM über die API befüllen.
*
* Die Skripte laufen unbeaufsichtigt über viele Subnetze. Festgehalten wird
* deshalb, was einen solchen Lauf beherrschbar hält: die parallele
* Detailerhebung, die Zeitgrenzen jedes blockierenden Schritts, die
* Plausibilitätsbremse gegen stellvertretend antwortende Netze und die Sperre
* gegen sich überholende Läufe.
*/
return static function (): void {
$script = static function (string $methode): string {
$klasse = new ReflectionClass(IpamScanAgentController::class);
$objekt = $klasse->newInstanceWithoutConstructor();
$m = new ReflectionMethod(IpamScanAgentController::class, $methode);
$m->setAccessible(true);
return (string)$m->invoke($objekt, 'https://doku.example.test');
};
$bash = static fn(): string => $script('agentScript');
$ps = static fn(): string => $script('agentScriptWindows');
test('Der Bash-Agent erhebt die Details parallel', function () use ($bash) {
$text = $bash();
assertTrue(str_contains($text, 'detail_one() {'), 'Die Detailerhebung je Host fehlt als eigene Funktion');
assertTrue(str_contains($text, '{ detail_one "$ip" > "$ddir/$ip.json"; } &'),
'Die Detailerhebung läuft nicht im Hintergrund');
assertTrue(str_contains($text, 'DETAIL_PARALLEL="${OPENNIT_DETAIL_PARALLEL:-16}"'),
'Die Obergrenze der gleichzeitigen Detailerhebungen fehlt');
// Jeder Host öffnet während des Portscans so viele Verbindungen, wie
// SCAN_PORTS Ports führt – die Detailphase läuft daher schmaler.
assertTrue(str_contains($text, 'PARALLEL="${OPENNIT_PARALLEL:-48}"'), 'Die Obergrenze der Pings fehlt');
});
test('Jeder blockierende Aufloesungsschritt hat eine Zeitgrenze', function () use ($bash) {
$text = $bash();
assertTrue(str_contains($text, 'lim() {'), 'Der Helfer für Zeitgrenzen fehlt');
// getent kennt selbst keine Grenze und wartet sonst die ganze
// resolv.conf ab.
assertTrue(str_contains($text, 'lim "$RDNS_TIMEOUT" getent hosts'), 'getent läuft ohne Zeitgrenze');
assertTrue(str_contains($text, 'lim "$RDNS_TIMEOUT" nmblookup'), 'nmblookup läuft ohne Zeitgrenze');
assertTrue(str_contains($text, 'lim "$RDNS_TIMEOUT" avahi-resolve-address'), 'avahi läuft ohne Zeitgrenze');
assertTrue(str_contains($text, 'RDNS="${OPENNIT_RDNS:-1}"'), 'Die Rückwärtsauflösung lässt sich nicht abschalten');
});
test('Ein vollstaendig antwortendes Subnetz wird nicht uebertragen', function () use ($bash, $ps) {
$text = $bash();
assertTrue(str_contains($text, 'MAX_ONLINE_PCT="${OPENNIT_MAX_ONLINE_PCT:-90}"'), 'Der Schwellwert fehlt');
assertTrue(str_contains($text, 'ALLOW_FULL_SUBNET="${OPENNIT_ALLOW_FULL_SUBNET:-0}"'),
'Die bewusste Ausnahme fehlt');
assertTrue(str_contains($text, 'Proxy-ARP-Verdacht'), 'Der Hinweis auf die Ursache fehlt');
assertTrue(str_contains($ps(), 'Proxy-ARP-Verdacht'), 'Der Windows-Agent kennt die Bremse nicht');
});
test('Ein abgebrochener Lauf markiert nichts als offline', function () use ($bash, $ps) {
// Nach einem Abbruch fehlen erreichbare Hosts in der Meldung; sie
// dürfen deshalb nicht als verschwunden gewertet werden.
$text = $bash();
assertTrue(str_contains($text, 'SUBNET_MAX_SECONDS="${OPENNIT_SUBNET_MAX_SECONDS:-900}"'),
'Das Zeitbudget je Subnetz fehlt');
assertTrue(str_contains($text, 'keine Offline-Markierung'), 'Die Offline-Markierung wird nicht ausgesetzt');
assertTrue(str_contains($ps(), '$off = $false'), 'Der Windows-Agent setzt die Markierung nicht aus');
});
test('Sich ueberholende Laeufe werden abgewiesen', function () use ($bash, $ps) {
$text = $bash();
assertTrue(str_contains($text, 'flock -n 9'), 'Die Sperrdatei fehlt');
assertTrue(str_contains($text, 'Ein Lauf ist bereits aktiv'), 'Der Hinweis auf den laufenden Scan fehlt');
// Ein übersprungener Lauf ist kein Fehler – sonst meldet ihn Cron.
assertTrue(str_contains($text, "exit 0"), 'Der abgewiesene Lauf endet nicht fehlerfrei');
assertTrue(str_contains($ps(), "New-Object System.Threading.Mutex"), 'Dem Windows-Agent fehlt die Sperre');
});
test('Beide Agenten fassen den Lauf am Ende zusammen', function () use ($bash, $ps) {
assertTrue(str_contains($bash(), 'log "Zusammenfassung'), 'Dem Bash-Agent fehlt die Zusammenfassung');
assertTrue(str_contains($ps(), 'Zusammenfassung'), 'Dem Windows-Agent fehlt die Zusammenfassung');
assertTrue(str_contains($bash(), 'TOTAL_MAX_SECONDS'), 'Das Zeitbudget je Lauf fehlt');
});
test('Der Windows-Agent loest die Namen gebuendelt auf', function () use ($ps) {
$text = $ps();
assertTrue(str_contains($text, 'function Get-HostNames'), 'Die gebündelte Auflösung fehlt');
assertTrue(str_contains($text, 'GetHostEntryAsync'), 'Die Auflösung blockiert weiterhin je Host');
assertTrue(str_contains($text, '$DnsTimeoutMs'), 'Die Zeitgrenze der Auflösung fehlt');
assertFalse(str_contains($text, 'Get-HostFor'), 'Die blockierende Einzelauflösung ist noch vorhanden');
});
test('Das Bash-Script ist syntaktisch gueltig', function () use ($bash) {
if (trim((string)@shell_exec('command -v bash 2>/dev/null')) === '') {
return; // ohne bash nicht prüfbar
}
$datei = tempnam(sys_get_temp_dir(), 'ipam-agent-');
file_put_contents($datei, $bash());
$ausgabe = [];
$code = 0;
exec('bash -n ' . escapeshellarg($datei) . ' 2>&1', $ausgabe, $code);
unlink($datei);
assertSame(0, $code, 'bash -n meldet: ' . implode("\n", $ausgabe));
});
};