/* ===================================================================== pruef-turn-wege.mjs — Es klingelt, aber es verbindet nicht. Filipe, 19.09.2026: „es klingelt erscheint auch alles bei jedem aber telefonieren klappt immer noch nicht was läuft ich will es heute an die leute bringen also los." --------------------------------------------------------------------- WAS VORHER GEMESSEN WURDE — UND WARUM ALLES GRÜN WAR Der Reihe nach, bevor eine Zeile Code angefasst wurde: coturn läuft aktiv STUN von außen antwortet, nennt die eigene Adresse echte Zuteilung von außen 7/7, das Testpaket kam an Dreier-Anruf im Prüflauf verbindet, alle hören alle Vier Messungen, vier Mal in Ordnung — und das Telefon ging trotzdem nicht. Das ist die unangenehmste Lage überhaupt, und sie hat genau einen Grund: Der Prüflauf fährt BEIDE Seiten auf demselben Rechner. Dort finden sie sich über die direkte Adresse, und die Vermittlung wird nie gebraucht. Draußen sitzen sie in verschiedenen Netzen. --------------------------------------------------------------------- DER FUND Eingetragen war eine einzige Adresse: `turn:159.195.212.167:3478`. Ein `turn:` OHNE `?transport=` heißt **UDP**, und nur UDP. Gemessen hört coturn aber auf beidem — 3478/UDP offen, 3478/TCP offen. Angeboten wurde nur der halbe Server. Wer in einem Netz sitzt, das UDP nach draußen sperrt (Mobilfunk mit strengem Profil, Gast-WLAN, Firmennetz), bekommt damit KEINEN Vermittlungsweg. Es klingelt — das läuft über die Website, also über 443 — und danach passiert nichts. --------------------------------------------------------------------- WAS DIESE PRÜFUNG SICHERT Nicht „gibt es einen TURN-Server" — das war schon vorher grün und hat nichts genützt. Sondern: **Kommt aus einem eingetragenen Eintrag wirklich jeder Weg heraus, und trägt jeder davon Zugangsdaten?** Der zweite Teil ist der wichtigere. Würde aufgefächert, ohne danach anzumelden, hätten zwei von drei Wegen kein Passwort — coturn weist sie ab, und wir hätten den Fehler nur verschoben. AUFRUF: node server/pruef-turn-wege.mjs Läuft in unter einer Sekunde, ohne Server, ohne Netz. ===================================================================== */ import { writeFileSync, unlinkSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { wegeAusfaechern, mitZugangsdaten, turnZugang } from "./workspace-turn.js"; let ok = 0, fehl = 0; const pruef = (name, bedingung, zusatz = "") => { if (bedingung) { ok++; console.log(` OK ${name}`); } else { fehl++; console.log(` FEHL ${name}${zusatz ? " — " + zusatz : ""}`); } }; console.log("\n=== Vermittlungswege: kommt jeder heraus, und trägt jeder ein Passwort? ===\n"); /* Ein Geheimnis nur für diese Prüfung -- nie das echte. Es wandert in eine Datei, weil mitZugangsdaten aus einer Datei liest; sie wird am Ende wieder entfernt. */ const DATEI = join(tmpdir(), `turn-pruef-${process.pid}.geheimnis`); writeFileSync(DATEI, "x".repeat(48), "utf8"); try { /* ---- 1. Ein schlichter Eintrag wird zu drei Wegen ---------------- */ const raus = wegeAusfaechern([{ urls: "turn:beispiel.de:3478" }]); const urls = raus.map((e) => String(e.urls)); pruef("aus einem Eintrag werden genau drei Wege", raus.length === 3, `es sind ${raus.length}: ${urls.join(", ")}`); pruef("darunter STUN — der Weg, der ohne Vermittlung auskommt", urls.includes("stun:beispiel.de:3478")); pruef("darunter der schnelle Weg (UDP)", urls.includes("turn:beispiel.de:3478?transport=udp")); pruef("darunter der Weg durch fast jede Sperre (TCP)", urls.includes("turn:beispiel.de:3478?transport=tcp"), "GENAU DIESER hat gefehlt — deshalb ging es nicht"); pruef("STUN steht vorn, TCP hinten", /^stun:/.test(urls[0]) && /transport=tcp$/.test(urls[2]), urls.join(" → ")); /* ---- 2. Und jeder turn:-Weg bekommt Zugangsdaten ----------------- */ const { adressen, turn_gesamt, turn_bereit } = mitZugangsdaten(raus, "w1", Date.now(), DATEI); pruef("beide turn:-Wege werden gezählt", turn_gesamt === 2, `gezählt: ${turn_gesamt}`); /* DIE ZAHL GEHOERT IN DIE BEDINGUNG, nicht nur in den Meldetext: Bei einer leeren Liste waere "alle haben ein Passwort" sonst trivialerweise wahr. */ pruef("und beide haben auch eins", turn_bereit === 2 && turn_gesamt === 2, `bereit: ${turn_bereit} von ${turn_gesamt}`); const mitPasswort = adressen.filter( (e) => /^turns?:/.test(String(e.urls)) && e.credential); pruef("nachgezählt an der ausgelieferten Liste", mitPasswort.length === 2, `${mitPasswort.length} von 2`); pruef("STUN bekommt KEINS — es kennt keine Anmeldung", !adressen.find((e) => /^stun:/.test(String(e.urls)))?.credential); /* ---- 3. Was ausdrücklich gemeint war, bleibt unangetastet -------- */ const fremd = wegeAusfaechern([ { urls: "turn:eigen.de:3478?transport=tcp" }, { urls: "stun:schon.da:3478" }, { urls: "turn:fremder-dienst.de:3478", username: "a", credential: "b" }, ]); pruef("ein Eintrag MIT transport bleibt, wie er ist", fremd.length === 3 && fremd.some((e) => e.urls === "turn:eigen.de:3478?transport=tcp")); pruef("ein fremder Dienst mit eigenem Passwort wird nicht angefasst", fremd.some((e) => e.urls === "turn:fremder-dienst.de:3478" && e.credential === "b")); /* ---- 4. Die Zugangsdaten sind zeitlich begrenzt ------------------ */ const z = turnZugang("w1", "x".repeat(48), Date.parse("2026-09-19T12:00:00Z")); pruef("der Benutzername trägt seinen Ablauf", /^\d+:w1$/.test(z.username), z.username); pruef("und er läuft wirklich ab", z.ablauf > Math.floor(Date.parse("2026-09-19T12:00:00Z") / 1000)); /* ---- DIE GEGENPROBEN --------------------------------------------- Eine Prüfung, die immer bestätigt, bestätigt nichts. */ console.log("\n --- Gegenproben (jede MUSS anschlagen) ---"); const ohneTcp = wegeAusfaechern([{ urls: "turn:x:1" }]) .filter((e) => !/transport=tcp/.test(String(e.urls))); pruef("Gegenprobe: fehlte der TCP-Weg, schlüge Prüfung 1 an", !ohneTcp.some((e) => /transport=tcp/.test(String(e.urls)))); const ohneGeheimnis = mitZugangsdaten(raus, "w1", Date.now(), join(tmpdir(), "gibt-es-nicht-" + process.pid)); pruef("Gegenprobe: ohne Geheimnis meldet die Rechnung 0 bereit und nennt den Grund", ohneGeheimnis.turn_bereit === 0 && !!ohneGeheimnis.grund, `bereit ${ohneGeheimnis.turn_bereit}, Grund "${ohneGeheimnis.grund}"`); } finally { try { unlinkSync(DATEI); } catch { /* egal */ } } console.log(`\n ${ok} bestanden, ${fehl} fehlgeschlagen\n`); process.exit(fehl ? 1 : 0);