Ein turn: ohne Zugangsdaten haette JEDEN Anruf verhindert, nicht nur die vermittelten
Gefunden am 25.09.2026 beim Bau des Podcast-Studios, das dieselbe
Rechnung benutzt, und dort im echten Browser gemessen:
Failed to construct 'RTCPeerConnection': Both username and credential
are required when the URL scheme is "turn" or "turns".
`new RTCPeerConnection(...)` WIRFT. Die Folge ist nicht "kein
Vermittlungsserver", sondern KEIN ANRUF -- auch nicht der, der direkt
zustande gekommen waere. Aus einem Randproblem (etwa jeder Fuenfte
braucht Vermittlung) waere ein Totalausfall fuer alle geworden,
ausgeloest davon, dass /etc/coturn-workspace.geheimnis einmal nicht
lesbar ist: Rechte verstellt, beim Neuaufbau vergessen, Datei leer.
server/workspace-turn.js laesst solche Eintraege bewusst stehen, damit
der Grund nicht verschwindet ("KEIN STILLES WEGLASSEN"). Das ist dort
richtig -- es heisst aber, dass im Browser aussortiert werden muss,
bevor die Verbindung gebaut wird. Genau das fehlte. Der Kommentar
daneben behauptete "ein turn: ohne Passwort schadet nicht"; das war
nie gemessen und ist falsch.
Zwei Aenderungen in workspace/assets/js/anruf.js:
1. Beim Uebernehmen der Adressen werden turn:/turns:-Eintraege ohne
Benutzer UND Passwort aussortiert. STUN bleibt -- es kennt gar keine
Anmeldung und ist der Weg, der in den meisten Faellen reicht. Der
Grund geht nicht verloren, er steht weiter in der Konsole (jetzt mit
dem Dateinamen, in dem man nachsehen muss).
2. `adressenFrisch()` sagt bei gesetztem Grund immer "nicht frisch".
Sonst waere die Liste fuer immer frisch: Ohne Zugangsdaten bleibt nur
STUN uebrig, und ein STUN-Eintrag hat keinen Ablaufzeitpunkt --
`every()` sagt dann "alles gueltig" und es wird nie wieder gefragt.
Waere die Datei auf dem Server repariert, telefonierte trotzdem
niemand mehr mit Vermittlung, bis er die Seite neu laedt. (Nebenbei
die zweite Falle derselben Zeile: `every()` auf einer LEEREN Liste
ist `true`.)
Geprueft in server/pruef-anruf.mjs: Die Pruefung liest den Filter aus
der ausgelieferten Datei und FUEHRT IHN AUS -- vier Adressen hinein,
zwei brauchbare heraus, mit Gegenprobe, dass er ueberhaupt etwas
wegnimmt. Eine Pruefung, die nur nachsieht, ob irgendwo "filter" steht,
waere auch dann gruen, wenn der Filter das Falsche tut.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
+54
-1
@@ -16,7 +16,7 @@
|
||||
|
||||
Aufrufen mit: node server/pruef-anruf.mjs
|
||||
===================================================================== */
|
||||
import { mkdtempSync, rmSync, writeFileSync, unlinkSync } from "node:fs";
|
||||
import { mkdtempSync, rmSync, writeFileSync, unlinkSync, readFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { request as httpAnfrage } from "node:http";
|
||||
@@ -1224,6 +1224,59 @@ melde("=== 8. Zu dritt ===");
|
||||
}
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
EIN turn: OHNE ZUGANGSDATEN DARF DEN BROWSER NICHT ERREICHEN
|
||||
(25.09.2026)
|
||||
|
||||
Am selben Tag beim Bau des Podcast-Studios im echten Browser
|
||||
nachgemessen:
|
||||
|
||||
Failed to construct 'RTCPeerConnection': Both username and
|
||||
credential are required when the URL scheme is "turn" or "turns".
|
||||
|
||||
`new RTCPeerConnection(...)` WIRFT. Die Folge ist nicht "kein
|
||||
Vermittlungsserver", sondern KEIN ANRUF -- auch nicht der, der direkt
|
||||
zustande gekommen waere. Aus einem Randproblem (etwa jeder Fuenfte
|
||||
braucht Vermittlung) wuerde ein Totalausfall fuer alle, ausgeloest
|
||||
davon, dass eine Datei auf dem Server unlesbar ist.
|
||||
|
||||
Der Server laesst solche Eintraege bewusst stehen, damit der Grund
|
||||
nicht verschwindet -- das ist dort richtig. Aussortiert wird im
|
||||
Browser, in `workspace/assets/js/anruf.js`.
|
||||
|
||||
DIESE PRUEFUNG LIEST KEINEN TEXT, SONDERN FUEHRT DEN ECHTEN AUSDRUCK
|
||||
AUS. Eine Prüfung, die nur nachsieht, ob irgendwo das Wort "filter"
|
||||
steht, waere auch dann gruen, wenn der Filter das Falsche tut.
|
||||
===================================================================== */
|
||||
{
|
||||
const quelle = readFileSync(new URL("../workspace/assets/js/anruf.js", import.meta.url), "utf8");
|
||||
const t = /adressen = \(d\.adressen \|\| \[\]\)(\.filter\([\s\S]*?\n {8}\}\);)/.exec(quelle);
|
||||
ok(!!t, "in anruf.js wird die Adressliste ueberhaupt gefiltert");
|
||||
if (t) {
|
||||
/* Genau der Ausdruck aus der ausgelieferten Datei, ausgefuehrt. */
|
||||
const filtern = new Function("d", "return (d.adressen || [])" + t[1] + ";");
|
||||
const rein = {
|
||||
adressen: [
|
||||
{ urls: "stun:1.2.3.4:3478" },
|
||||
{ urls: "turn:1.2.3.4:3478?transport=udp", username: "1:a", credential: "x" },
|
||||
{ urls: "turn:1.2.3.4:3478?transport=tcp" },
|
||||
{ urls: "turns:1.2.3.4:5349", username: "1:a" },
|
||||
],
|
||||
};
|
||||
const raus = filtern(rein);
|
||||
ok(raus.length === 2, `es bleiben genau die zwei brauchbaren uebrig (${raus.length})`);
|
||||
ok(raus.some((a) => a.urls.startsWith("stun:")),
|
||||
"STUN bleibt drin -- es kennt gar keine Anmeldung und reicht meistens");
|
||||
ok(raus.every((a) => !/^turns?:/.test(a.urls) || (a.username && a.credential)),
|
||||
"kein turn: ohne Benutzer UND Passwort geht hinaus");
|
||||
/* GEGENPROBE: Wuerde der Filter alles durchlassen, waere die Zeile
|
||||
oben auch gruen. Hier wird bewiesen, dass er wirklich etwas
|
||||
wegnimmt. */
|
||||
ok(raus.length < rein.adressen.length,
|
||||
"Gegenprobe: der Filter nimmt tatsaechlich etwas weg");
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------- */
|
||||
melde("");
|
||||
melde(`${geprueft} Pruefungen, ${fehler} Fehler`
|
||||
|
||||
@@ -1259,8 +1259,25 @@
|
||||
immer kommt. */
|
||||
let telefonGehtHier = true;
|
||||
|
||||
/* Der Grund, den der Server zum Vermittlungsserver mitschickt --
|
||||
leer heisst "alles in Ordnung". Er steht hier oben, weil ihn
|
||||
`adressenFrisch()` braucht. */
|
||||
let turnGrund = '';
|
||||
|
||||
function adressenFrisch() {
|
||||
if (!adressenGeholt) return false;
|
||||
/* FEHLT DAS GEHEIMNIS, GILT DIE LISTE NIE ALS FRISCH.
|
||||
|
||||
Sonst waere sie es fuer immer: Ohne Zugangsdaten bleibt nach dem
|
||||
Aussortieren nur STUN uebrig, und ein STUN-Eintrag hat keinen
|
||||
Ablaufzeitpunkt -- `every()` sagt dann "alles noch gueltig" und
|
||||
es wird nie wieder gefragt. Waere die Datei auf dem Server
|
||||
zwischenzeitlich repariert, telefonierte trotzdem niemand mehr
|
||||
mit Vermittlung, bis er die Seite neu laedt.
|
||||
|
||||
(Nebenbei die zweite Falle derselben Zeile: `every()` auf einer
|
||||
LEEREN Liste ist `true`. Auch dagegen hilft diese Abfrage.) */
|
||||
if (turnGrund) return false;
|
||||
const jetzt = Math.floor(Date.now() / 1000);
|
||||
return adressen.every((a) => {
|
||||
const m = /^(\d{10,}):/.exec(a && a.username || '');
|
||||
@@ -1277,15 +1294,47 @@
|
||||
if (a.status === 404) { telefonGehtHier = false; return; }
|
||||
if (a.ok) {
|
||||
const d = await a.json();
|
||||
adressen = d.adressen || [];
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
EIN turn: OHNE ZUGANGSDATEN DARF NICHT IN DIE LISTE.
|
||||
|
||||
Am 25.09.2026 im echten Browser nachgemessen (beim Bau des
|
||||
Podcast-Studios, das dieselbe Rechnung benutzt):
|
||||
|
||||
Failed to construct 'RTCPeerConnection': Both username and
|
||||
credential are required when the URL scheme is "turn" or
|
||||
"turns".
|
||||
|
||||
`new RTCPeerConnection(...)` WIRFT. Die Folge ist nicht etwa
|
||||
"kein Vermittlungsserver", sondern KEIN ANRUF -- auch nicht
|
||||
der, der direkt zustande gekommen waere. Aus einem
|
||||
Randproblem (etwa jeder fuenfte braucht Vermittlung) wuerde
|
||||
ein Totalausfall fuer alle.
|
||||
|
||||
Der Server laesst solche Eintraege bewusst stehen, damit der
|
||||
Grund nicht verschwindet (server/workspace-turn.js, Absatz
|
||||
"KEIN STILLES WEGLASSEN"). Das ist dort richtig -- aber es
|
||||
heisst, dass HIER aussortiert werden muss, bevor gebaut wird.
|
||||
Der Grund geht nicht verloren: Er steht weiter unten in der
|
||||
Konsole.
|
||||
|
||||
STUN bleibt selbstverstaendlich drin -- STUN kennt gar keine
|
||||
Anmeldung, und es ist der Weg, der in den meisten Faellen
|
||||
reicht.
|
||||
═══════════════════════════════════════════════════════════ */
|
||||
adressen = (d.adressen || []).filter((e) => {
|
||||
if (!/^turns?:/.test(String(e && e.urls))) return true;
|
||||
return !!(e.username && e.credential);
|
||||
});
|
||||
nurGleichesNetz = !!d.nur_gleiches_netz;
|
||||
gruppeMax = d.gruppe_max || 4;
|
||||
adressenGeholt = true;
|
||||
/* Ein eingetragener Vermittlungsserver, der niemanden
|
||||
durchlässt, ist schlimmer als keiner -- er sieht aus, als
|
||||
wäre alles eingerichtet. Deshalb steht er in der Konsole. */
|
||||
if (d.turn_grund) {
|
||||
console.warn('[anruf] Vermittlungsserver ohne Zugangsdaten:', d.turn_grund);
|
||||
turnGrund = d.turn_grund || '';
|
||||
if (turnGrund) {
|
||||
console.warn('[anruf] Vermittlungsserver ohne Zugangsdaten:', turnGrund,
|
||||
'- Anrufe gehen nur noch direkt. Auf dem Server nachsehen: /etc/coturn-workspace.geheimnis');
|
||||
}
|
||||
}
|
||||
} catch { /* dann eben ohne -- im selben Netz geht es trotzdem */ }
|
||||
|
||||
Reference in New Issue
Block a user