diff --git a/server/helfer-port.mjs b/server/helfer-port.mjs index f8b7d9a8..bc115e80 100644 --- a/server/helfer-port.mjs +++ b/server/helfer-port.mjs @@ -185,7 +185,10 @@ const PRO_DATEI = 2; * Bereichs (BASIS bis BASIS+5000 in Schritten von 2, bei derzeit * rund 180 Dateien also bis etwa 5400) -- zwei Pruefungen koennen * sich dadurch nicht ins Gehege kommen, auch wenn beide ausweichen. */ -const AUSWEICHEN = 4000; +/* AUSGELEITET, damit pruef-ports.mjs mit DERSELBEN Zahl rechnet. + Eine 4000 dort waere die zweite Fassung -- und beim naechsten + Mal stuende in einer der beiden eine andere. */ +export const AUSWEICHEN = 4000; /** Der Port dieser Datei -- abgeleitet, nicht vergeben. * @@ -222,7 +225,10 @@ const AUSWEICHEN = 4000; NEBENBEFUND: Auch im alten, von Hand vergebenen Bereich lag eine -- die 4190 (ManageSieve). Sie war nie vergeben, rein zufaellig. ===================================================================== */ -const GESPERRT = new Set([ +/* AUSGELEITET aus demselben Grund wie AUSWEICHEN: pruef-ports.mjs + weist nach, dass keine abgeleitete Nummer hier drinsteht -- und + muss dafuer die echte Liste ansehen, nicht eine Abschrift. */ +export const GESPERRT = new Set([ 1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, 77, 79, 87, 95, 101, 102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 137, 138, 139, 143, 161, 179, 389, 427, 465, 512, 513, 514, 515, 526, 530, 531, diff --git a/server/pruef-ports.mjs b/server/pruef-ports.mjs new file mode 100644 index 00000000..cf293f05 --- /dev/null +++ b/server/pruef-ports.mjs @@ -0,0 +1,187 @@ +/* ===================================================================== + KANN JEDE PRÜFUNG IHREN PORT ÜBERHAUPT BEKOMMEN? + ===================================================================== + + ENTSTANDEN AUS EINEM ABSTURZ, DEN ICH SELBST AUSGELÖST HABE + (22./23.09.2026). + + Die Portnummer einer Prüfung wird aus ihrer STELLE IM ALPHABET + abgeleitet (helfer-port.mjs). Das ist eindeutig von der Bauart her + — zwei Dateien können nicht dieselbe Stelle haben — und genau + deshalb gut. Es hat aber eine Nebenwirkung, die dort auch + ausdrücklich steht: Kommt eine neue Prüfung dazu, verschieben sich + ALLE Nummern dahinter. + + In der Nacht zum 23.09. sind sechs neue Prüfdateien entstanden. + `pruef-bereiche-lesend` rutschte dadurch auf Port 5040 — und den + hält auf diesem Rechner ein Windows-Dienst. Der Lauf endete mit + + [uncaughtException] Error: listen EACCES 127.0.0.1:5040 + + also einem Stapelauszug aus node:net, der wie ein Fehler im Code + aussieht. Gefunden habe ich es durch Zufall: Ich wollte nur sehen, + ob eine andere Änderung etwas kaputt gemacht hat. + + --------------------------------------------------------------------- + WARUM DAS EINE EIGENE PRÜFUNG IST UND KEINE ZEILE IN EINER ANDEREN + + Weil die Frage nicht „ist mein Code richtig" lautet, sondern „kann + hier überhaupt gemessen werden". Das ist der dritte Ausgang, und er + betrifft ALLE Prüfungen gleichzeitig. Eine einzelne kann ihn für + sich beantworten (das tut `portMussFreiSein` seit dem 23.09. auch); + dass der ganze Satz durchläuft, sieht man erst, wenn man den ganzen + Satz ansieht. Das dauert unter einer Sekunde. + + --------------------------------------------------------------------- + BELEGT IST ETWAS ANDERES ALS GESPERRT — und nur eines davon ist ein + Befund: + + EACCES Das System gibt den Port nicht her. Das geht nicht + vorbei. Hier MUSS der Ausweichport frei sein, sonst + ist diese Prüfung auf diesem Rechner nicht lauffähig. + EADDRINUSE Jemand hört gerade darauf — meist ein Lauf, der eben + zu Ende ging und dessen Server noch schließt. Das + geht vorbei, und es als Fehler zu melden wäre eine + Warnung, die immer kommt. Deshalb der dritte Ausgang. + + Der Unterschied ist der ganze Punkt. Würde hier beides gleich + behandelt, wäre diese Prüfung im Gesamtlauf regelmäßig rot — und + dann liest niemand mehr, wenn sie einmal recht hat. + ===================================================================== */ +import { readdirSync } from "node:fs"; +import { createServer } from "node:net"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { notbremse } from "./helfer-notbremse.mjs"; + +notbremse(60_000, "pruef-ports"); + +const HIER = dirname(fileURLToPath(import.meta.url)); + +let fehler = 0, geprueft = 0, offen = 0; +const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; +const unklar = (t) => { offen++; console.log(" -- " + t); }; + +const { portNummer, AUSWEICHEN, GESPERRT } = await import("./helfer-port.mjs"); + +console.log("=== Kann jede Pruefung ihren Port bekommen? ==="); +console.log(""); + +/* DIE LISTE WIRD GELESEN, NICHT GEPFLEGT -- und zwar mit derselben + Regel wie in helfer-port.mjs selbst: alle `pruef-*.mjs` im + Serverordner, sortiert. Eine eigene Liste hier wäre die, die beim + Hinzufügen der nächsten Prüfung nicht mitwächst -- also genau der + Fehler, den diese Datei verhindern soll. */ +const dateien = readdirSync(HIER) + .filter((f) => f.startsWith("pruef-") && f.endsWith(".mjs")) + .sort(); + +ok(dateien.length > 50, `${dateien.length} Pruefdateien gefunden`); +ok(dateien.includes("pruef-ports.mjs"), + "diese Datei steht selbst in der Liste -- sonst waere die Regel eine andere" + + " als die, nach der die Nummern vergeben werden"); + +/** Was sagt das Betriebssystem zu diesem Port? */ +const lage = (port) => new Promise((fertig) => { + const s = createServer(); + s.once("error", (f) => fertig(f.code || "FEHLER")); + s.once("listening", () => s.close(() => fertig(null))); + s.listen(port, "127.0.0.1"); +}); + +/* ===================================================================== + 1. DIE ABLEITUNG SELBST + ===================================================================== */ +console.log("--- Die Nummern ---"); + +const nummern = []; +for (let i = 0; i < dateien.length; i++) { + for (const nr of [0, 1]) nummern.push({ datei: dateien[i], nr, port: portNummer(i * 2 + nr) }); +} +ok(nummern.length === dateien.length * 2, + `${nummern.length} Nummern fuer ${dateien.length} Dateien (je zwei)`); + +const doppelt = nummern + .map((x) => x.port) + .filter((p, i, alle) => alle.indexOf(p) !== i); +ok(nummern.length > 0 && doppelt.length === 0, + `keine Nummer doppelt (${doppelt.length} Ueberschneidungen)`); + +const inGesperrt = nummern.filter((x) => GESPERRT.has(x.port)); +ok(nummern.length > 0 && inGesperrt.length === 0, + `keine liegt auf einem gesperrten Port (${inGesperrt.map((x) => x.port).join(", ") || "keine"})`); + +/* GEGENPROBE ZUR ABLEITUNG: Ein gesperrter Port MUSS uebersprungen + werden. Ohne diese Zeile waere „keine liegt auf einem gesperrten" + auch dann wahr, wenn die Sperrliste gar nicht angesehen wird. */ +{ + const bekannt = [...GESPERRT].filter((p) => p >= 5000).sort((a, b) => a - b)[0]; + if (bekannt === undefined) { + unklar("kein gesperrter Port oberhalb von 5000 in der Liste --" + + " die Gegenprobe hat nichts, woran sie sich zeigen koennte"); + } else { + const alle = new Set(nummern.map((x) => x.port)); + ok(!alle.has(bekannt), + `Gegenprobe: der gesperrte Port ${bekannt} kommt in keiner Nummer vor`); + } +} + +/* ===================================================================== + 2. UND WAS DAS SYSTEM DAZU SAGT + ===================================================================== */ +console.log(""); +console.log("--- Was der Rechner hergibt ---"); + +const gesperrt = [], belegt = []; +for (const x of nummern) { + const l = await lage(x.port); + if (l === "EACCES") gesperrt.push(x); + else if (l !== null) belegt.push({ ...x, grund: l }); +} + +ok(nummern.length > 0, + `${nummern.length} Ports durchprobiert, ${gesperrt.length} vom System gesperrt,` + + ` ${belegt.length} gerade belegt`); + +/* EIN GESPERRTER PORT IST KEIN BEFUND -- solange das Ausweichen + greift. `portMussFreiSein` nimmt dann Port + AUSWEICHEN. Was hier + geprueft wird, ist genau das: dass dieser Weg offensteht. */ +for (const x of gesperrt) { + const ersatz = x.port + AUSWEICHEN; + const l2 = await lage(ersatz); + ok(l2 === null, + `${x.datei} (Nr. ${x.nr}): Port ${x.port} ist vom System gesperrt,` + + ` der Ausweichport ${ersatz} ist ${l2 === null ? "frei" : "es auch nicht (" + l2 + ")"}`); +} +if (!gesperrt.length) { + ok(true, "kein einziger Port ist vom System gesperrt"); +} + +/* BELEGT GEHT VORBEI. Kein Fehler, sondern der dritte Ausgang: Hier + laesst sich gerade nicht sagen, ob die Nummer brauchbar ist. */ +for (const x of belegt.slice(0, 5)) { + unklar(`${x.datei} (Nr. ${x.nr}): Port ${x.port} ist gerade belegt (${x.grund})` + + " -- das geht vorbei, hier ist nichts zu entscheiden"); +} +if (belegt.length > 5) { + unklar(`... und ${belegt.length - 5} weitere belegte Ports`); +} + +/* ===================================================================== + 3. DER TEUERSTE FALL: ZWEI LAEUFE GLEICHZEITIG + ===================================================================== + Sind SEHR viele Ports belegt, laeuft vermutlich ein zweiter + Prueflauf. Das ist keine Aussage ueber den Code, aber eine, die man + sehen will -- am 06.09.2026 haben zwei gleichzeitige Laeufe im + anderen Projekt 16 Fehlschlaege erzeugt, keiner davon echt. + ===================================================================== */ +if (belegt.length > nummern.length / 4) { + unklar(`${belegt.length} von ${nummern.length} Ports sind belegt --` + + " laeuft parallel noch ein zweiter Prueflauf? Dessen Ergebnisse" + + " und diese hier waeren beide unbrauchbar."); +} + +console.log(""); +console.log(`${fehler === 0 ? "BESTANDEN" : "FEHLGESCHLAGEN"}` + + ` -- ${geprueft} geprueft, ${fehler} Fehler, ${offen} nicht nachsehbar`); +process.exit(fehler === 0 ? 0 : 1);