Vermittlungsserver: coturn mit Zugangsdaten, die verfallen
Damit Anrufe auch in Netzen zustande kommen, die keine direkte Verbindung zulassen (15-25 % der Faelle). coturn ist installiert, steht aber still, bis die Konfiguration liegt -- ein coturn mit Werkseinstellung ist ein offenes Relais. KEIN FESTES PASSWORT. Es laege dauerhaft im Browser jedes Team-Mitglieds und liesse sich nie entziehen. Der Server rechnet stattdessen bei jeder Abfrage Zugangsdaten, die nach zwoelf Stunden verfallen (server/workspace-turn.js, coturns `use-auth-secret`). Das Geheimnis liegt in einer Datei, nicht in der Datenbank: einstellung- Setzen() schreibt Werte ins Protokoll, und coturn braucht denselben Wert ohnehin in /etc. Die Oberflaeche frischt die Daten vor jedem Anruf auf. Der Chat ist eine App, die tagelang offen bleibt -- wer nur beim Laden holt, telefoniert am zweiten Tag ohne Vermittlung, und es faellt nicht auf: Es scheitern nur die, die sie gebraucht haetten. DIE WICHTIGSTE ZEILE DER KONFIGURATION ist die Sperrliste. Gemessen: dreizehn Dienste lauschen auf diesem Server nur oertlich, darunter Caddys Verwaltung auf 127.0.0.1:2019 -- wer sie erreicht, kann jede Website umleiten. Ohne Sperrliste waere der Vermittlungsserver die Tuer dorthin, und die Anfrage saehe fuer Caddy aus wie von localhost. Geprueft: pruef-anruf.mjs 51 -> 73 Pruefungen. Gegenprobe (Geheimnis als Passwort ausliefern + fremde Zugangsdaten ueberschreiben) macht genau 5 rot, darunter "das Geheimnis steht NIRGENDS in der Antwort". tools/turn-probelauf.sh beweist am echten coturn, was ein fester Vergleichswert nicht kann: dass coturn unsere Rechnung akzeptiert. Beide Seiten koennten sonst konsequent falsch rechnen und jede Pruefung waere gruen. Seine eigene Gegenprobe war zweimal zu Recht rot -- der erste Aufbau mass wegen `-y` gar nicht das Ziel, das er zu messen behauptete, sondern coturns eingebauten Loopback-Schutz. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
/* =====================================================================
|
||||
DEN VERMITTLUNGSSERVER EINTRAGEN UND NACHMESSEN -- 18.09.2026
|
||||
|
||||
Aufrufen (auf dem Server, als Benutzer dogiweb):
|
||||
|
||||
node tools/turn-einrichten.mjs pruefen
|
||||
node tools/turn-einrichten.mjs eintragen turn:159.195.212.167:3478
|
||||
node tools/turn-einrichten.mjs entfernen
|
||||
|
||||
---------------------------------------------------------------------
|
||||
WARUM EIN BEFEHL UND KEIN EINGABEFELD
|
||||
|
||||
Die Adresse wird einmal eingetragen und dann jahrelang nicht mehr.
|
||||
Ein Menuepunkt dafuer waere eine Seite, die niemand findet, wenn er
|
||||
sie braucht -- und eine, die gepflegt werden muss. Der Befehl steht
|
||||
dagegen neben den anderen Einrichtungsschritten, in derselben
|
||||
Anleitung.
|
||||
|
||||
WAS DIESER BEFEHL NICHT TUT: Er erzeugt das Geheimnis nicht. Das
|
||||
gehoert root, weil es auch in /etc/turnserver.conf muss. Hier wird
|
||||
nur NACHGESEHEN, ob es aus Sicht des Dienstes lesbar ist -- der
|
||||
haeufigste Fehler beim Einrichten sind Dateirechte, und der faellt
|
||||
sonst erst beim ersten Anruf auf, der nicht zustande kommt.
|
||||
|
||||
DAS GEHEIMNIS WIRD NIE AUSGEGEBEN, auch nicht gekuerzt.
|
||||
===================================================================== */
|
||||
|
||||
import { createSocket } from "node:dgram";
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { turnGeheimnisLesen, TURN_GEHEIMNIS_DATEI } from "../server/workspace-turn.js";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const DB_PFAD = process.env.WORKSPACE_DB
|
||||
|| join(__dirname, "..", "..", "workspace-daten", "workspace.db");
|
||||
|
||||
const [, , befehl, wert] = process.argv;
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
EIN ECHTER VERSUCH, KEIN BLICK IN DIE KONFIGURATION
|
||||
|
||||
Eine Konfigurationsdatei zu lesen beweist, dass jemand etwas
|
||||
hingeschrieben hat. Ob der Dienst laeuft, ob die Firewall offen ist
|
||||
und ob er von aussen antwortet, beweist nur eine Anfrage.
|
||||
|
||||
Ein STUN-Binding ist dafuer die kleinste moegliche: 20 Byte hin,
|
||||
und zurueck kommt die eigene Adresse, wie der Server sie sieht.
|
||||
--------------------------------------------------------------------- */
|
||||
function stunFragen(host, port = 3478, frist = 3000) {
|
||||
return new Promise((fertig) => {
|
||||
const kennung = randomBytes(12);
|
||||
const anfrage = Buffer.alloc(20);
|
||||
anfrage.writeUInt16BE(0x0001, 0); // Binding Request
|
||||
anfrage.writeUInt16BE(0x0000, 2); // keine Attribute
|
||||
anfrage.writeUInt32BE(0x2112a442, 4); // der feste Erkennungswert
|
||||
kennung.copy(anfrage, 8);
|
||||
|
||||
const s = createSocket("udp4");
|
||||
const uhr = setTimeout(() => {
|
||||
try { s.close(); } catch { /* schon zu */ }
|
||||
fertig({ ok: false, grund: "keine Antwort innerhalb von 3 Sekunden" });
|
||||
}, frist);
|
||||
|
||||
s.on("error", (f) => {
|
||||
clearTimeout(uhr);
|
||||
try { s.close(); } catch { /* schon zu */ }
|
||||
fertig({ ok: false, grund: f.message });
|
||||
});
|
||||
|
||||
s.on("message", (a) => {
|
||||
clearTimeout(uhr);
|
||||
try { s.close(); } catch { /* schon zu */ }
|
||||
if (a.length < 20 || a.readUInt16BE(0) !== 0x0101) {
|
||||
return fertig({ ok: false, grund: "Antwort ist kein STUN-Erfolg" });
|
||||
}
|
||||
if (!a.subarray(8, 20).equals(kennung)) {
|
||||
return fertig({ ok: false, grund: "Antwort gehoert zu einer anderen Anfrage" });
|
||||
}
|
||||
/* XOR-MAPPED-ADDRESS heraussuchen -- die Adresse ist mit dem
|
||||
Erkennungswert verodert, damit unterwegs niemand daran
|
||||
herumbastelt. */
|
||||
let p = 20;
|
||||
while (p + 4 <= a.length) {
|
||||
const art = a.readUInt16BE(p), laenge = a.readUInt16BE(p + 2);
|
||||
if (art === 0x0020 && laenge >= 8) {
|
||||
const hafen = a.readUInt16BE(p + 6) ^ 0x2112;
|
||||
const ip = [0, 1, 2, 3]
|
||||
.map((i) => a[p + 8 + i] ^ [0x21, 0x12, 0xa4, 0x42][i]).join(".");
|
||||
return fertig({ ok: true, gesehenAls: `${ip}:${hafen}` });
|
||||
}
|
||||
p += 4 + laenge + ((4 - (laenge % 4)) % 4);
|
||||
}
|
||||
fertig({ ok: true, gesehenAls: "(ohne Adressangabe)" });
|
||||
});
|
||||
|
||||
s.send(anfrage, port, host, (f) => {
|
||||
if (f) {
|
||||
clearTimeout(uhr);
|
||||
try { s.close(); } catch { /* schon zu */ }
|
||||
fertig({ ok: false, grund: f.message });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function adressenLesen(db) {
|
||||
try {
|
||||
const r = db.prepare("SELECT wert FROM einstellungen WHERE schluessel = 'anruf_adressen'").get();
|
||||
const l = JSON.parse(r?.wert || "[]");
|
||||
return Array.isArray(l) ? l : [];
|
||||
} catch { return []; }
|
||||
}
|
||||
|
||||
function adressenSchreiben(db, liste) {
|
||||
db.prepare(`
|
||||
INSERT INTO einstellungen (schluessel, wert, geaendert, von)
|
||||
VALUES ('anruf_adressen', ?, ?, NULL)
|
||||
ON CONFLICT(schluessel) DO UPDATE SET
|
||||
wert = excluded.wert, geaendert = excluded.geaendert`)
|
||||
.run(JSON.stringify(liste), new Date().toISOString());
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
if (!["pruefen", "eintragen", "entfernen"].includes(befehl)) {
|
||||
console.log("Aufruf: node tools/turn-einrichten.mjs pruefen"
|
||||
+ "\n node tools/turn-einrichten.mjs eintragen turn:159.195.212.167:3478"
|
||||
+ "\n node tools/turn-einrichten.mjs entfernen");
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const db = new DatabaseSync(DB_PFAD);
|
||||
|
||||
if (befehl === "eintragen") {
|
||||
if (!/^(stun|turn|turns):[^\s]+$/.test(wert || "")) {
|
||||
console.error("FEHLT: eine Adresse wie turn:159.195.212.167:3478");
|
||||
process.exit(2);
|
||||
}
|
||||
const alt = adressenLesen(db);
|
||||
/* Doppelte Eintraege vermeiden -- zweimal dieselbe Adresse bedeutet,
|
||||
dass jeder Browser sie zweimal probiert. */
|
||||
const neu = alt.filter((e) => e.urls !== wert).concat([{ urls: wert }]);
|
||||
adressenSchreiben(db, neu);
|
||||
console.log(`Eingetragen. Vorher ${alt.length}, jetzt ${neu.length} Adresse(n).`);
|
||||
} else if (befehl === "entfernen") {
|
||||
adressenSchreiben(db, []);
|
||||
console.log("Alle Adressen entfernt.");
|
||||
}
|
||||
|
||||
/* ---------- Der Zustand, immer ---------------------------------------- */
|
||||
console.log("");
|
||||
console.log("=== Zustand ===");
|
||||
|
||||
const adressen = adressenLesen(db);
|
||||
console.log(`Eingetragene Adressen: ${adressen.length}`);
|
||||
for (const a of adressen) console.log(` ${a.urls}`);
|
||||
|
||||
const g = turnGeheimnisLesen();
|
||||
console.log("");
|
||||
if (g.wert) {
|
||||
console.log(`Geheimnis: lesbar (${g.wert.length} Zeichen) aus ${TURN_GEHEIMNIS_DATEI}`);
|
||||
} else {
|
||||
console.log(`Geheimnis: NICHT verwendbar -- ${g.grund}`);
|
||||
console.log(` erwartet unter ${TURN_GEHEIMNIS_DATEI}`);
|
||||
if (g.grund.startsWith("unlesbar")) {
|
||||
console.log(" (Dateirechte: root:dogiweb mit 640)");
|
||||
}
|
||||
}
|
||||
|
||||
/* Wirklich anfragen -- bei jeder eingetragenen Adresse. */
|
||||
let erreichbar = 0, versucht = 0;
|
||||
for (const a of adressen) {
|
||||
const m = /^(?:stun|turns?):([^:/?]+)(?::(\d+))?/.exec(a.urls || "");
|
||||
if (!m) continue;
|
||||
versucht++;
|
||||
const antwort = await stunFragen(m[1], Number(m[2] || 3478));
|
||||
if (antwort.ok) {
|
||||
erreichbar++;
|
||||
console.log(`\n${a.urls}: antwortet. Von dort aus sehen wir aus wie ${antwort.gesehenAls}`);
|
||||
} else {
|
||||
console.log(`\n${a.urls}: KEINE ANTWORT -- ${antwort.grund}`);
|
||||
console.log(" Meist eins von dreien: Dienst laeuft nicht "
|
||||
+ "(systemctl status coturn), Firewall zu (ufw), oder falsche Adresse.");
|
||||
}
|
||||
}
|
||||
|
||||
db.close();
|
||||
|
||||
console.log("");
|
||||
if (!adressen.length) {
|
||||
console.log("ERGEBNIS: Keine Adresse eingetragen -- Anrufe klappen nur im selben Netz.");
|
||||
process.exit(1);
|
||||
}
|
||||
if (!g.wert) {
|
||||
console.log("ERGEBNIS: Adresse da, aber ohne Geheimnis weist coturn jeden ab.");
|
||||
process.exit(1);
|
||||
}
|
||||
if (versucht && erreichbar < versucht) {
|
||||
console.log(`ERGEBNIS: ${erreichbar} von ${versucht} Adressen antworten.`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("ERGEBNIS: Eingetragen, Geheimnis lesbar, Server antwortet.");
|
||||
@@ -0,0 +1,186 @@
|
||||
#!/bin/bash
|
||||
# =====================================================================
|
||||
# BEWEIST, DASS COTURN UNSERE ZUGANGSDATEN AKZEPTIERT -- 18.09.2026
|
||||
#
|
||||
# Aufrufen auf dem Server: bash tools/turn-probelauf.sh
|
||||
#
|
||||
# ---------------------------------------------------------------------
|
||||
# WARUM ES DIESES SKRIPT GIBT
|
||||
#
|
||||
# `pruef-anruf.mjs` prueft die Rechenvorschrift gegen einen festen
|
||||
# Vergleichswert. Das schuetzt davor, dass sie sich unbemerkt aendert --
|
||||
# es beweist aber NICHT, dass coturn das Ergebnis akzeptiert. Wir
|
||||
# koennten beide Seiten konsequent falsch rechnen, und jede Pruefung
|
||||
# waere gruen, waehrend kein einziger Anruf ueber die Vermittlung
|
||||
# zustande kaeme. Das faellt im Betrieb nicht auf: Es scheitern nur die
|
||||
# 15-25 %, die den Server ueberhaupt brauchen.
|
||||
#
|
||||
# Hier laeuft deshalb ein echter coturn und ein echter Client.
|
||||
#
|
||||
# ---------------------------------------------------------------------
|
||||
# DASS ES NICHTS ANFASST, IST ABSICHT
|
||||
#
|
||||
# Eigenes Heimatverzeichnis, eigener Port, nur 127.0.0.1, kein root,
|
||||
# kein Systemdienst. Der laufende coturn auf 3478 bleibt unberuehrt,
|
||||
# und von aussen ist der Probe-Server nicht erreichbar.
|
||||
# =====================================================================
|
||||
set -u
|
||||
P="${TMPDIR:-/tmp}/turn-probe-$$"
|
||||
FEHLER=0
|
||||
ok() { echo " ok $1"; }
|
||||
fehl() { echo " FEHL $1"; FEHLER=$((FEHLER+1)); }
|
||||
|
||||
aufraeumen() { pkill -f "$P/turn.conf" 2>/dev/null; kill "${GEGENPART:-0}" 2>/dev/null; sleep 1; rm -rf "$P"; }
|
||||
trap aufraeumen EXIT
|
||||
|
||||
command -v turnserver >/dev/null || { echo "KANN NICHT NACHSEHEN: coturn ist nicht installiert."; exit 3; }
|
||||
command -v turnutils_uclient >/dev/null || { echo "KANN NICHT NACHSEHEN: turnutils_uclient fehlt."; exit 3; }
|
||||
NODE=$(command -v node || echo /usr/local/bin/node)
|
||||
[ -x "$NODE" ] || { echo "KANN NICHT NACHSEHEN: node nicht gefunden."; exit 3; }
|
||||
|
||||
mkdir -p "$P"
|
||||
|
||||
# ZIEL FUER DEN SPERRTEST -- und warum ausgerechnet dieses.
|
||||
# Der erste Anlauf nahm 127.0.0.1. Die Gegenprobe (Abschnitt 4) zeigte:
|
||||
# Auch OHNE Sperrliste kam 403 -- coturn verbietet Loopback-Ziele von
|
||||
# sich aus. Der Abschnitt haette also NICHT unsere Sperrliste gemessen,
|
||||
# sondern eine eingebaute Regel, und waere gruen geblieben, wenn jemand
|
||||
# die Liste entfernt. Genau dafuer ist die Gegenprobe da.
|
||||
# Die Docker-Bruecke ist ein echtes oertliches Ziel auf diesem Server
|
||||
# und KEIN Loopback -- ohne unsere Liste waere sie erreichbar.
|
||||
ZIEL="${TURN_PROBE_ZIEL:-172.17.0.1}"
|
||||
|
||||
# WARUM HIER KEIN `-y` STEHT (und im ersten Anlauf stand):
|
||||
# `-y` heisst "client-to-client" -- der Client verbindet dann zwei
|
||||
# EIGENE Kanaele miteinander und ignoriert `-e` vollstaendig. Das Ziel
|
||||
# war damit immer die Relay-Adresse 127.0.0.1, also Loopback, und das
|
||||
# sperrt coturn von sich aus. Die Gegenprobe blieb deshalb zweimal rot,
|
||||
# ohne dass an der Sperrliste etwas falsch war. Erst die Hilfeseite des
|
||||
# Werkzeugs hat es gezeigt -- nicht das dritte Herumprobieren.
|
||||
# Jetzt laeuft ein echter Gegenpart auf der Docker-Bruecke.
|
||||
gegenpart_starten() {
|
||||
turnutils_peer -L "$ZIEL" -p 3480 >/dev/null 2>&1 &
|
||||
GEGENPART=$!
|
||||
sleep 1
|
||||
}
|
||||
gegenpart_beenden() { kill "$GEGENPART" 2>/dev/null; }
|
||||
GEHEIM=$(head -c 24 /dev/urandom | od -An -tx1 | tr -d ' \n')
|
||||
|
||||
# Die Konfiguration entspricht deploy/turnserver.conf; abweichend nur
|
||||
# Adresse, Ports und dass das Protokoll in den Probeordner geht.
|
||||
schreibe_konf() { # $1 = "mit-sperre" oder "ohne-sperre"
|
||||
cat > "$P/turn.conf" <<CONF
|
||||
listening-ip=127.0.0.1
|
||||
relay-ip=127.0.0.1
|
||||
listening-port=34780
|
||||
no-tls
|
||||
no-dtls
|
||||
min-port=49400
|
||||
max-port=49420
|
||||
use-auth-secret
|
||||
static-auth-secret=$GEHEIM
|
||||
realm=dogfather-universe.com
|
||||
no-multicast-peers
|
||||
no-tcp-relay
|
||||
no-cli
|
||||
user-quota=12
|
||||
total-quota=100
|
||||
CONF
|
||||
if [ "$1" = "mit-sperre" ]; then
|
||||
cat >> "$P/turn.conf" <<'CONF'
|
||||
denied-peer-ip=127.0.0.0-127.255.255.255
|
||||
denied-peer-ip=10.0.0.0-10.255.255.255
|
||||
denied-peer-ip=172.16.0.0-172.31.255.255
|
||||
denied-peer-ip=192.168.0.0-192.168.255.255
|
||||
CONF
|
||||
fi
|
||||
}
|
||||
|
||||
starten() {
|
||||
turnserver -c "$P/turn.conf" --no-stdout-log --log-file="$P/turn.log" >/dev/null 2>&1 &
|
||||
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
||||
ss -lnu 2>/dev/null | grep -q 34780 && return 0
|
||||
sleep 0.5
|
||||
done
|
||||
return 1
|
||||
}
|
||||
anhalten() { pkill -f "$P/turn.conf" 2>/dev/null; sleep 1; }
|
||||
|
||||
# Dieselbe Rechnung wie server/workspace-turn.js -- bewusst nachgebaut
|
||||
# statt importiert: Wenn beide Seiten aus derselben Zeile kaemen, waere
|
||||
# ein Fehler in dieser Zeile hier unsichtbar.
|
||||
zugang() {
|
||||
"$NODE" -e "
|
||||
const {createHmac} = require('node:crypto');
|
||||
const u = (Math.floor(Date.now()/1000) + 12*3600) + ':w7';
|
||||
console.log(u + '\t' + createHmac('sha1', process.argv[1]).update(u).digest('base64'));
|
||||
" "$GEHEIM"
|
||||
}
|
||||
|
||||
echo "=== Probelauf gegen einen echten coturn ==="
|
||||
echo
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
echo "--- 1. Die Konfiguration aus deploy/turnserver.conf ---"
|
||||
schreibe_konf mit-sperre
|
||||
gegenpart_starten
|
||||
if starten; then ok "coturn startet und lauscht auf 127.0.0.1:34780"
|
||||
else fehl "coturn startet nicht -- $(tail -3 "$P/turn.log" 2>/dev/null | tr '\n' ' ')"; exit 1; fi
|
||||
|
||||
A=$(turnutils_stunclient -p 34780 127.0.0.1 2>&1)
|
||||
echo "$A" | grep -q "reflexive addr" \
|
||||
&& ok "er beantwortet eine STUN-Anfrage ($(echo "$A" | grep -m1 -o '127[0-9.:]*'))" \
|
||||
|| fehl "keine STUN-Antwort: $(echo "$A" | head -2 | tr '\n' ' ')"
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
echo
|
||||
echo "--- 2. Die Kernfrage: nimmt coturn unsere gerechneten Daten an? ---"
|
||||
L=$(zugang); U=$(echo "$L" | cut -f1); W=$(echo "$L" | cut -f2)
|
||||
RICHTIG=$(turnutils_uclient -u "$U" -w "$W" -p 34780 -n 2 -c -e "$ZIEL" -r 3480 127.0.0.1 2>&1)
|
||||
FALSCH=$(turnutils_uclient -u "$U" -w "nicht-das-richtige-passwort" -p 34780 -n 1 -c -e "$ZIEL" -r 3480 127.0.0.1 2>&1)
|
||||
|
||||
# Der Unterschied ist eindeutig und genau der richtige:
|
||||
# Mit gueltigen Daten kommt der Client UEBER die Allocation hinaus und
|
||||
# scheitert erst am gesperrten Ziel. Mit ungueltigen scheitert schon
|
||||
# die Allocation selbst.
|
||||
echo "$RICHTIG" | grep -q "Cannot complete Allocation" \
|
||||
&& fehl "unsere Zugangsdaten werden ABGEWIESEN -- kein Anruf ginge ueber die Vermittlung" \
|
||||
|| ok "unsere Zugangsdaten werden angenommen (die Zuteilung gelingt)"
|
||||
|
||||
echo "$FALSCH" | grep -q "Cannot complete Allocation" \
|
||||
&& ok "ein falsches Passwort wird abgewiesen -- die Pruefung prueft wirklich" \
|
||||
|| fehl "ein FALSCHES Passwort kam durch -- der Server steht jedem offen"
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
echo
|
||||
echo "--- 3. Die Sperrliste: kommt jemand an die oertlichen Dienste? ---"
|
||||
echo "$RICHTIG" | grep -qi "403" \
|
||||
&& ok "ein Ziel im oertlichen Netz ($ZIEL) wird verweigert (403)" \
|
||||
|| fehl "$ZIEL war als Ziel ERLAUBT -- ein Weg zu jedem oertlichen Dienst"
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
echo
|
||||
echo "--- 4. Gegenprobe: ohne Sperrliste MUSS derselbe Versuch durchgehen ---"
|
||||
echo " (sonst haette Abschnitt 3 nur gemessen, dass irgendetwas scheitert)"
|
||||
anhalten
|
||||
schreibe_konf ohne-sperre
|
||||
if starten; then
|
||||
L=$(zugang); U=$(echo "$L" | cut -f1); W=$(echo "$L" | cut -f2)
|
||||
OHNE=$(turnutils_uclient -u "$U" -w "$W" -p 34780 -n 4 -c -e "$ZIEL" -r 3480 127.0.0.1 2>&1)
|
||||
echo "$OHNE" | grep -qi "403" \
|
||||
&& fehl "auch ohne Sperrliste 403 -- Abschnitt 3 beweist dann nichts" \
|
||||
|| ok "ohne Sperrliste laeuft derselbe Versuch durch -- die Sperre war die Ursache"
|
||||
echo "$OHNE" | grep -qiE "total transmit|tot_send_msgs|Total transmit time" \
|
||||
&& ok "und es fliessen wirklich Daten ueber die Weiterleitung" \
|
||||
|| echo " -- Datenfluss nicht ablesbar (nicht schlimm, der Rest steht)"
|
||||
else
|
||||
fehl "zweiter Start misslungen"
|
||||
fi
|
||||
|
||||
echo
|
||||
if [ "$FEHLER" -eq 0 ]; then
|
||||
echo "ALLES IN ORDNUNG -- coturn und der Workspace rechnen dasselbe."
|
||||
exit 0
|
||||
fi
|
||||
echo "NICHT IN ORDNUNG -- $FEHLER Punkt(e)."
|
||||
exit 1
|
||||
Reference in New Issue
Block a user