Vier Werkzeuge haben nachweislich Unsinn ins Kundenprotokoll geschrieben. Belege
sind echte Prod-Messungen; jeder Fix wurde im Emulator gegen den passenden
Fehlerfall nachgestellt.
SNMP (Snmp.kt, snmp.ts)
Snmp.get lieferte bei JEDEM Problem null -> "-" -> parseInt("-")||0 -> 0.
Ein Switch, der gar nicht antwortet, stand damit als "0 Fehler" in Gruen im
Kunden-PDF (Prod #95/#96). Neu: Ergebnistyp Value/Timeout/Failure statt
String?, Request-ID- und error-status-Pruefung, v2c-Ausnahmen (0x80/0x81/0x82)
erkannt, ein Wiederholversuch bei Timeout. Das Werkzeug meldet jetzt
"nicht messbar" statt Zahlen zu erfinden.
Test: SNMP gegen einen Host ohne Agent -> status 3 "keine SNMP-Antwort".
Traceroute (pingWithTtl, traceroute.ts)
Der Fallback-Regex \((\d+\.\d+\.\d+\.\d+)\) traf IMMER zuerst die ping-Kopfzeile
"PING 8.8.8.8 (8.8.8.8) ...". Ein stummer Hop wurde dadurch als Ziel ausgegeben
und der Trace beendet — daher "2 Hops bis 8.8.8.8, beide 0 ms" (Prod #13).
Zwischen-Hops liefern zudem nie ein time=, die Zeit war strukturell 0.
Neu: Kopfzeile verworfen, zeilenweise geparst, Reverse-DNS-Antworten erkannt,
RTT selbst per nanoTime gestoppt, 3 Proben je TTL, waitFor(3s)+destroyForcibly,
"keine Antwort" ist null und nicht 0, Bewertung haengt an "Ziel erreicht".
Test: 8.8.8.8 -> 1 Hop mit echten 13,3 ms (statt 0 ms);
192.0.2.1 -> status 3, alle Hops "* (keine Antwort)" statt gruen.
Ping (measurePing, ping.ts)
Kein WifiLock, keine Aufwaermproben: zwischen den Proben schlief das Funkmodul
ein, die Aufwachzeit landete in der Messung — 296 ms zum LAN-Gateway bei 0 %
Verlust und rotem Status (Prod #4). WAKE_LOCK fehlte im Manifest, weshalb
WifiLock.acquire() eine SecurityException warf, die ein leerer catch schluckte.
Neu: WAKE_LOCK im Manifest, WifiLock (LOW_LATENCY) + PARTIAL_WAKE_LOCK um jede
Messung, 2 verworfene Aufwaermproben, Median und Messaufschlag (avg-min)
zusaetzlich ausgewiesen, Verlust als Kommazahl statt Ganzzahl-Division,
Bewertung auf den Median statt den Mittelwert, "keine Antwort" != "0 ms".
Test: 20 Proben -> Median 0,3 ms, Messaufschlag 0,1 ms, Verfahren dokumentiert.
Durchsatz (measureThroughput, iperf.ts)
Kein soTimeout: nahm die Gegenstelle an, ohne zu senden, blockierte read()
unbegrenzt und das Werkzeug-Fenster hing dauerhaft auf "Messung laeuft ...".
Neu: soTimeout 4 s je Phase, harter withTimeout-Guard, Ist-Zeit statt
angenommener halber Dauer, measured-Flag — ohne uebertragene Bytes gibt es
kein rotes "0 Mbit/s" mehr, sondern "nicht messbar".
Test: Gegenstelle nimmt an und schweigt -> Ergebnis nach 30 s statt Dauerhaenger.
Netzerkennung (firstLocalIpv4, getLocalSubnet, hostsInSubnet)
firstLocalIpv4() gab bei fehlendem Netz hart "192.168.1.1" zurueck; zusammen mit
dem Praefix-Fallback /24 entstand das Phantom-Netz 192.168.1.0/24, das auch noch
ins Protokoll geschrieben wurde. Der Fehlerzweig in ipscan.ts konnte deshalb nie
greifen. Neu: "" statt Phantom-IP, Link-Local uebersprungen, getLocalSubnet
meldet einen Fehler, DHCP-Fallback nur bei WLAN-Transport (sonst wurde bei
gestecktem USB-RJ45 die Maske aus dem WLAN genommen), unlesbares CIDR-Praefix
wird abgelehnt statt still durch /24 ersetzt.
Test: Flugmodus -> "Kein Netzbereich — WLAN/LAN nicht aktiv?" statt Phantom-Netz.
Stresstest-Ziel (stresstest.ts)
Vorgabe 192.168.1.1 im Kundennetz 192.168.178.0/24 -> 5 Minuten ins Leere
gemessen, Ergebnis "100 % Verlust" rot im Protokoll (Prod #126). Neu: kein
fester Vorgabewert, leer = Gateway des aktiven Adapters, und eine Vorlaufprobe
bricht ab, bevor minutenlang gegen ein totes Ziel gemessen wird.
Test: Ziel 192.168.1.1 -> nach 7 s "antwortet nicht — Test nicht gestartet"
(vorher: 60 s Messung mit rotem 100-%-Ergebnis).
Neue Bewertung "nicht messbar" (MeasureStatus 3)
Bisher gab es nur ok/warn/fail. Ein Test, der gar nicht durchgefuehrt werden
konnte, musste sich als eines davon ausgeben — meist als Gruen. Status 3 wird
neutral grau dargestellt (Messkarte und Geraetekarte) und ist keine Aussage
ueber das Kundennetz.
Geprueft: gradle compileDebugKotlin ok, svelte-check 0 Fehler (2 vorbestehende
Warnungen), alle sieben Faelle im Emulator (Pixel 6 / Android 14) durchgespielt
und die Ergebnisse in der Datenbank gegengelesen statt am Bildschirm geraten.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
77 lines
2.6 KiB
TypeScript
77 lines
2.6 KiB
TypeScript
/**
|
|
* Tool: Durchsatz-Test — misst die Bandbreite gegen eine Gegenstelle.
|
|
*
|
|
* ACHTUNG: Der Test spricht **kein** iperf3-Protokoll. Nativ wird schlicht auf
|
|
* einen TCP-Socket geschrieben bzw. von ihm gelesen — ohne Cookie, ohne
|
|
* Parameterblock, ohne Zustandsautomat. Gegen einen echten iperf3-Server bricht
|
|
* die Verbindung deshalb ab; er braucht eine einfache TCP-Sink/Source-Gegenstelle.
|
|
* Umbau (eigenes Protokoll oder mitgeliefertes iperf3-Binary) steht in
|
|
* ROADMAP_UMSETZUNG.md Phase 1.
|
|
*/
|
|
|
|
import { scanner } from '../../scanner';
|
|
import type { MeasureStatus, Tool } from '../types';
|
|
|
|
export const iperfTool: Tool = {
|
|
id: 'iperf',
|
|
category: 'internet',
|
|
name: 'Durchsatz-Test',
|
|
icon: 'gauge-circle',
|
|
description: 'Misst Down-/Upload-Bandbreite gegen eine Gegenstelle.',
|
|
scope: 'protocol',
|
|
params: [
|
|
{ key: 'host', label: 'Gegenstelle (IP)', type: 'text', placeholder: '192.168.1.20' },
|
|
{ key: 'port', label: 'Port', type: 'number', default: 5201 },
|
|
{
|
|
key: 'duration',
|
|
label: 'Dauer (Sek.)',
|
|
type: 'select',
|
|
default: '10',
|
|
options: [
|
|
{ value: '5', label: '5 Sekunden' },
|
|
{ value: '10', label: '10 Sekunden' },
|
|
{ value: '30', label: '30 Sekunden' },
|
|
],
|
|
},
|
|
],
|
|
async run(ctx) {
|
|
const host = String(ctx.params.host ?? '');
|
|
if (!host) throw new Error('Keine Gegenstelle angegeben');
|
|
const port = Number(ctx.params.port || 5201);
|
|
const durationSec = Number(ctx.params.duration || 10);
|
|
|
|
const res = await scanner.throughput({ host, port, durationSec });
|
|
|
|
// Kein einziges Byte übertragen = es gab keine Messung. Daraus „0 Mbit/s"
|
|
// in Rot zu machen wäre eine Aussage über das Kundennetz, die die Messung
|
|
// gar nicht hergibt — meist fehlt schlicht die Gegenstelle.
|
|
if (res.measured === false) {
|
|
return {
|
|
label: `${host}:${port} — keine Gegenstelle`,
|
|
result: {
|
|
gegenstelle: `${host}:${port}`,
|
|
hinweis:
|
|
'Es wurden keine Daten übertragen. Der Test braucht eine Gegenstelle, ' +
|
|
'die auf diesem Port Daten annimmt und sendet. Ein echter iperf3-Server ' +
|
|
'funktioniert hier NICHT (siehe Modul-Doku).',
|
|
},
|
|
measureStatus: 3, // nicht messbar
|
|
};
|
|
}
|
|
|
|
let status: MeasureStatus = 0;
|
|
if (res.downMbps < 100) status = 1;
|
|
if (res.downMbps < 10) status = 2;
|
|
|
|
return {
|
|
label: `↓ ${res.downMbps} Mbit/s · ↑ ${res.upMbps} Mbit/s`,
|
|
result: {
|
|
gegenstelle: `${host}:${port}`,
|
|
downloadMbps: res.downMbps,
|
|
uploadMbps: res.upMbps,
|
|
dauerSekunden: durationSec,
|
|
},
|
|
measureStatus: status,
|
|
};
|
|
},
|
|
};
|