Files
dogfather-universe/server/pruef-portnummern.mjs
T
DogFatherGitandClaude Opus 5 445ea88f2b fetch verweigert Port 5060 -- und sagt dasselbe wie ein toter Server
Nach der Umstellung auf abgeleitete Nummern bekam pruef-chat-kanaele
die 5060 und stuerzte ab:

  TypeError: fetch failed

Der Server lief nachweislich ("Server laeuft auf http://127.0.0.1:5060"),
die Anmeldung ueber `http.request` hatte sogar geklappt, und BASIS war
sauber ("http://127.0.0.1:5060", 21 Zeichen, PORT eine Zahl). Nur die
`fetch`-Aufrufe scheiterten.

5060 ist SIP und steht auf der "bad ports"-Liste der
Fetch-Spezifikation. Node hat sie uebernommen: `fetch` baut dorthin
keine Verbindung auf, noch bevor ein Paket fliegt. Die Ursache steht
nur in `.cause` -- "bad port" statt "ECONNREFUSED". Die Meldung
obendrueber ist beide Male dieselbe, und wer sie liest, sucht am
Server.

Gegengeprueft mit einem echten Zuhoerer auf beiden Nummern:
  5060 -> fetch failed (bad port)
  5062 -> fetch failed (ECONNREFUSED)   <- also normal erreichbar

`eigenerPort` ueberspringt die gesperrten Nummern jetzt (portNummer).
Uebersprungen wird nach der REGEL, nicht nach einer Tabelle von
Sonderfaellen: die n-te brauchbare Nummer ab 5000. Eindeutig bleibt es,
weil die Reihenfolge feststeht.

pruef-portnummern (11 statt 8) prueft es doppelt: gegen die Liste UND
am echten Verhalten -- ein Zuhoerer auf 5060 muss fuer `fetch` tot
sein, einer auf einer abgeleiteten Nummer erreichbar. Ohne die zweite
Haelfte waere die Liste nur eine Behauptung ueber Node, und
Behauptungen altern.

Nebenbefund: Auch im alten, von Hand vergebenen Bereich lag eine
gesperrte Nummer -- die 4190. Sie war nie vergeben, rein zufaellig.

pruef-chat-kanaele: 79 Pruefungen, 0 Fehler -- wieder auf der
Grundlinie, die vor der Umstellung gemessen wurde.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-20 23:06:52 +02:00

144 lines
6.9 KiB
JavaScript

/* =====================================================================
pruef-portnummern.mjs — Zwei Pruefungen, eine Nummer
WAS HIER SCHIEFGING (20.09.2026 gemessen):
164 Pruefdateien, 129 verschiedene Nummern -- NEUNZEHN waren doppelt
vergeben, 4186 und 4188 und 4196 und 4198 sogar dreifach. Niemand
hatte das gewollt; jede neue Pruefung wurde von einer vorhandenen
abgeschrieben, und die Nummer kam mit.
WAS DAS ANRICHTET: Der Waechter bricht ab, statt gegen einen fremden
Server zu messen -- so weit, so richtig. Aber zwei Pruefungen mit
derselben Nummer koennen nie gleichzeitig laufen, und ein
liegengebliebener Prozess der einen laesst die andere mit einer
Meldung abbrechen, die wie ein Befund aussieht. Genau das ist am
20.09. zweimal passiert, beide Male mit Fehlersuche an der falschen
Stelle.
WAS DIESE PRUEFUNG SICHERT: Dass die Nummer ABGELEITET bleibt. Eine
von Hand eingetragene Zahl ist der Anfang der naechsten Doppelung --
sie faellt nur niemandem auf, bis zwei Laeufe kollidieren.
===================================================================== */
import { readdirSync, readFileSync } from "node:fs";
import { eigenerPort, portNummer } from "./helfer-port.mjs";
let ok_ = 0, fehl = 0;
const ok = (b, text) => { if (b) { ok_++; console.log(" ok " + text); }
else { fehl++; console.log(" FEHL " + text); } };
const ORDNER = new URL(".", import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1");
const dateien = readdirSync(ORDNER)
.filter((f) => f.startsWith("pruef-") && f.endsWith(".mjs")).sort();
console.log("\n=== 1. Niemand traegt eine Nummer von Hand ein ===");
const vonHand = [];
const ohneWaechter = [];
for (const f of dateien) {
const s = readFileSync(ORDNER + "/" + f, "utf8");
/* Kommentare zaehlen nicht: In helfer-port.mjs und hier stehen alte
Nummern als Beleg, und ein Beleg ist kein Eintrag. */
const code = s.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
if (/portMussFreiSein\(\s*\d/.test(code)) vonHand.push(f);
const startetServer = /listen\(|process\.env\.PORT\s*=/.test(code);
if (startetServer && !/eigenerPort\(\s*import\.meta/.test(code)) ohneWaechter.push(f);
}
ok(dateien.length > 100, `${dateien.length} Pruefdateien angesehen`);
ok(vonHand.length === 0,
`keine traegt eine feste Nummer im Waechter (${vonHand.slice(0, 4).join(", ") || "—"})`);
ok(ohneWaechter.length === 0,
`jede, die einen Server startet, leitet ihre Nummer ab (${ohneWaechter.slice(0, 4).join(", ") || "—"})`);
console.log("\n=== 2. Die abgeleiteten Nummern sind eindeutig ===");
const belegt = new Map();
let doppelt = 0;
dateien.forEach((f, i) => {
for (let n = 0; n < 2; n++) {
const p = portNummer(i * 2 + n);
if (belegt.has(p)) { doppelt++; console.log(` ${p}: ${belegt.get(p)} und ${f}`); }
belegt.set(p, f);
}
});
ok(belegt.size === dateien.length * 2 && doppelt === 0,
`${belegt.size} Nummern fuer ${dateien.length} Dateien, ${doppelt} doppelt`);
console.log("\n=== 2b. Keine davon ist eine, die `fetch` verweigert ===");
/* DIE TEUERSTE HALBE STUNDE DIESES ABENDS. Nach der Umstellung bekam
pruef-chat-kanaele die 5060 und stuerzte ab -- obwohl der Server
nachweislich lief und die Anmeldung ueber `http.request` geklappt
hatte. 5060 ist SIP und steht auf der "bad ports"-Liste der
Fetch-Spezifikation, die Node uebernommen hat: `fetch` baut dorthin
keine Verbindung auf.
Die Meldung lautet "fetch failed" -- genau wie bei einem Server, der
nicht laeuft. Wer sie liest, sucht am Server. Deshalb steht sie
jetzt hier als eigene Pruefung: Die naechste Datei, die in so eine
Nummer rutscht, soll es HIER erfahren und nicht in einer
Fehlersuche. */
const BOESE = [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, 532, 540, 548, 554, 556, 563, 587, 601, 636, 989, 990, 993,
995, 1719, 1720, 1723, 2049, 3659, 4045, 4190, 5060, 5061, 6000, 6566, 6665,
6666, 6667, 6668, 6669, 6679, 6697, 10080];
const getroffen = [...belegt.keys()].filter((x) => BOESE.includes(x));
ok(getroffen.length === 0,
`keine der ${belegt.size} Nummern steht auf der Sperrliste (${getroffen.join(", ") || "—"})`);
/* GEGENPROBE AM ECHTEN VERHALTEN, nicht an der Liste: Ein Zuhoerer auf
einer gesperrten Nummer muss fuer `fetch` unerreichbar sein, einer
daneben erreichbar. Ohne das waere die Liste oben nur eine
Behauptung ueber Node -- und Behauptungen altern. */
{
const { createServer } = await import("node:http");
const probe = async (nummer) => {
const dienst = createServer((q, a) => { a.writeHead(200); a.end("da"); });
await new Promise((r) => dienst.listen(nummer, "127.0.0.1", r));
let grund = "erreichbar";
try { await fetch(`http://127.0.0.1:${nummer}/`); }
catch (f) { grund = f?.cause?.message || f?.message || "?"; }
await new Promise((r) => dienst.close(r));
return grund;
};
const gesperrt = await probe(5060);
const erlaubt = await probe(portNummer(0));
ok(/bad port/i.test(gesperrt),
`eine gesperrte Nummer ist fuer fetch tot, obwohl jemand lauscht (5060: ${gesperrt})`);
ok(erlaubt === "erreichbar",
`eine abgeleitete dagegen nicht (${portNummer(0)}: ${erlaubt})`);
}
console.log("\n=== 3. Die Ableitung stimmt wirklich (gemessen, nicht gerechnet) ===");
/* NICHT die Formel gegen sich selbst pruefen -- das waere eine
Bestaetigung ohne Aussage. Gefragt wird die Funktion, die die
Pruefungen auch benutzen, und verglichen wird mit der Stelle im
Alphabet. */
const ersteDatei = dateien[0];
const meine = dateien.indexOf("pruef-portnummern.mjs");
const p0 = await eigenerPort(import.meta, "pruef-portnummern (Selbsttest)", 0);
ok(p0 === portNummer(meine * 2),
`meine eigene Nummer ist ${p0} — Stelle ${meine} im Alphabet, erste Datei ${ersteDatei}`);
const p1 = await eigenerPort(import.meta, "pruef-portnummern (Selbsttest)", 1);
ok(p1 === p0 + 1, `und die zweite ist ${p1}, also genau eine daneben`);
console.log("\n=== 4. Gegenprobe: kann sie auch NICHT IN ORDNUNG sagen? ===");
/* Eine Pruefung, die immer bestaetigt, bestaetigt nichts. */
let drittesAusgangGriff = false;
try {
/* Eine Datei, die es nicht gibt -- die Ableitung darf dann NICHT
raten, sondern muss abbrechen. */
await eigenerPort({ url: new URL("./gibt-es-nicht.mjs", import.meta.url).href }, "Gegenprobe");
} catch (f) {
drittesAusgangGriff = /steht nicht in der Liste/.test(f?.message || "");
}
ok(drittesAusgangGriff, "eine unbekannte Datei bekommt keine geratene Nummer, sondern einen Abbruch");
let zuHoheNummer = false;
try { await eigenerPort(import.meta, "Gegenprobe", 7); }
catch (f) { zuHoheNummer = /Nummern je Datei/.test(f?.message || ""); }
ok(zuHoheNummer, "und eine dritte Nummer je Datei gibt es nicht");
console.log(`\n${ok_} Pruefungen, ${fehl} Fehler`);
console.log(fehl ? "NICHT IN ORDNUNG\n" : "ALLES IN ORDNUNG\n");
process.exit(fehl ? 1 : 0);