Radio: es war nie UDP -- Discord verlangt DAVE
Deploy / check (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s

Der Mitschnitt von 12:33 hat es benannt:

  Netzteil: Websocket auf -> Anmeldung -> zu -> Websocket zu (4017) -> zu

4017 heisst DAVE. Discord erzwingt seit dem 01.03.2026 Ende-zu-Ende-
Verschluesselung in allen Nicht-Stage-Sprachkanaelen und weist Gegenstellen,
die das nicht koennen, sofort nach der Anmeldung ab -- lange bevor irgendein
UDP-Paket fliegt. Nachgeschlagen bei Discord, nicht gedeutet.

Gemessen, warum es uns traf: @discordjs/voice 0.18.0 kennt DAVE ueberhaupt
nicht (null Fundstellen im Bundle) und spricht Sprach-Gateway v4. Es meldet
deshalb max_dave_protocol_version: 0. 0.19.2 spricht v8, bringt
@snazzah/davey als feste Abhaengigkeit mit und hat 48 Fundstellen.

Also hochgezogen. Im gebauten Abbild gegengeprueft, weil das native Modul
plattformabhaengig ist und der Lock auf Windows entstanden ist:

  DAVE Libraries
  - @snazzah/davey: 0.1.12

@snazzah/davey-linux-x64-gnu steht im Lock, node:22-slim ist Debian/glibc,
das Modul laedt im Abbild. engines steht jetzt auf >=22.12.0 -- das verlangt
0.19.2, und ">=20" waere ab jetzt gelogen.

Und ein Fehler in der frischen Diagnose selbst, den erst der Ernstfall zeigte:
sie hat den Schritt nicht benannt, obwohl der Mitschnitt ihn enthielt. Das
Netzteil schliesst sich erst und meldet dann den Code, also endet jeder
Mitschnitt auf "zu" -- und genau den nahm die Auswertung als letzten Schritt.
Damit lief alles in "Ursache offen". Jetzt wird "zu" uebersprungen; die
Gegenprobe mit 4009 landet wieder beim UDP-Handschlag statt im Nichts.

Nebenbei stand "zu" zweimal im Mitschnitt, einmal vor und einmal nach dem
Schliesscode: die Entdopplung verglich mit dem letzten Eintrag statt mit dem
letzten Schritt.

Der Weg hierher gehoert zur Sache: erst hiess es "ausgehendes UDP dicht" --
geraten. Dann wurde gemessen, dass UDP rauskommt, und die Meldung sagte
ehrlich "Ursache offen". Diese eine ehrliche Meldung hat den Schliesscode
sichtbar gemacht, und der war die Antwort. Eine Pruefung, die nichts sehen
kann, meldet nicht "sauber" und auch nicht "UDP".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-14 12:39:52 +02:00
co-authored by Claude Opus 5
parent 484f2edc62
commit f08a0e8b35
3 changed files with 354 additions and 19 deletions
+328 -15
View File
@@ -9,7 +9,7 @@
"version": "0.1.0",
"license": "MIT",
"dependencies": {
"@discordjs/voice": "^0.18.0",
"@discordjs/voice": "^0.19.2",
"@fastify/cookie": "^11.1.2",
"@fastify/multipart": "^10.1.0",
"@fastify/static": "^10.1.2",
@@ -121,30 +121,25 @@
}
},
"node_modules/@discordjs/voice": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/@discordjs/voice/-/voice-0.18.0.tgz",
"integrity": "sha512-BvX6+VJE5/vhD9azV9vrZEt9hL1G+GlOdsQaVl5iv9n87fkXjf3cSwllhR3GdaUC8m6dqT8umXIWtn3yCu4afg==",
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@discordjs/voice/-/voice-0.19.2.tgz",
"integrity": "sha512-3yJ255e4ag3wfZu/DSxeOZK1UtnqNxnspmLaQetGT0pDkThNZoHs+Zg6dgZZ19JEVomXygvfHn9lNpICZuYtEA==",
"license": "Apache-2.0",
"dependencies": {
"@types/ws": "^8.5.12",
"discord-api-types": "^0.37.103",
"@snazzah/davey": "^0.1.9",
"@types/ws": "^8.18.1",
"discord-api-types": "^0.38.41",
"prism-media": "^1.3.5",
"tslib": "^2.6.3",
"ws": "^8.18.0"
"tslib": "^2.8.1",
"ws": "^8.19.0"
},
"engines": {
"node": ">=18"
"node": ">=22.12.0"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/voice/node_modules/discord-api-types": {
"version": "0.37.120",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.120.tgz",
"integrity": "sha512-7xpNK0EiWjjDFp2nAhHXezE4OUWm7s1zhc/UXXN6hnFFU8dfoPHgV0Hx0RPiCa3ILRpdeh152icc68DGCyXYIw==",
"license": "MIT"
},
"node_modules/@discordjs/ws": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz",
@@ -180,6 +175,18 @@
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@emnapi/core": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz",
"integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.3",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
@@ -190,6 +197,17 @@
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/wasi-threads": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz",
"integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@fastify/accept-negotiator": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.0.1.tgz",
@@ -1000,6 +1018,27 @@
"node": ">=8"
}
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz",
"integrity": "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==",
"license": "MIT",
"optional": true,
"dependencies": {
"@tybys/wasm-util": "^0.10.3"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=23.5.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
},
"peerDependencies": {
"@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4",
"@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4"
}
},
"node_modules/@nodable/entities": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.2.0.tgz",
@@ -1063,6 +1102,270 @@
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/@snazzah/davey": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/@snazzah/davey/-/davey-0.1.12.tgz",
"integrity": "sha512-V+NlX5931RwVamZhhEfZekMdcvXDKdMAmHW1AuGaykVQsNyBOq3bpmGpoKRBDCYgFWKIufJ0Dcg3m4cYhvUy6g==",
"license": "MIT",
"engines": {
"node": ">= 10"
},
"funding": {
"url": "https://github.com/sponsors/Snazzah"
},
"optionalDependencies": {
"@snazzah/davey-android-arm-eabi": "0.1.12",
"@snazzah/davey-android-arm64": "0.1.12",
"@snazzah/davey-darwin-arm64": "0.1.12",
"@snazzah/davey-darwin-x64": "0.1.12",
"@snazzah/davey-freebsd-x64": "0.1.12",
"@snazzah/davey-linux-arm-gnueabihf": "0.1.12",
"@snazzah/davey-linux-arm64-gnu": "0.1.12",
"@snazzah/davey-linux-arm64-musl": "0.1.12",
"@snazzah/davey-linux-x64-gnu": "0.1.12",
"@snazzah/davey-linux-x64-musl": "0.1.12",
"@snazzah/davey-wasm32-wasi": "0.1.12",
"@snazzah/davey-win32-arm64-msvc": "0.1.12",
"@snazzah/davey-win32-ia32-msvc": "0.1.12",
"@snazzah/davey-win32-x64-msvc": "0.1.12"
}
},
"node_modules/@snazzah/davey-android-arm-eabi": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/@snazzah/davey-android-arm-eabi/-/davey-android-arm-eabi-0.1.12.tgz",
"integrity": "sha512-6VC/an+Sx5dI5skb+90rYcIB1jhm48Rl0nDaw0UNT4bz1rMjpVfmmZqeocYXMq96IdbBMlE6OTKGcBm2C3gkQg==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@snazzah/davey-android-arm64": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/@snazzah/davey-android-arm64/-/davey-android-arm64-0.1.12.tgz",
"integrity": "sha512-0Bwd03/JsTFhlPhF4q/LW0RxSzntFpQdhz+TBdFljYSg8IEyA38saPJeTNjpIgDfhAumPzvhCdfS6O5qT8yXDw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@snazzah/davey-darwin-arm64": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/@snazzah/davey-darwin-arm64/-/davey-darwin-arm64-0.1.12.tgz",
"integrity": "sha512-lKMV6ITi9BQLt0fx/pAT7M8xcojVK7bryVJGdaW3bq8gABFslS3ti/KzrWabQvhpEV71FZe5mV0UcKHFVaTsZw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@snazzah/davey-darwin-x64": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/@snazzah/davey-darwin-x64/-/davey-darwin-x64-0.1.12.tgz",
"integrity": "sha512-vXXc/eW/e3TQeb7VsdtrPqs3/22j0aSqiP1ZXmZtDjQRBwgSxwItWYa6sh5MELP2EHB2igNlGzB6Hc0XlbGi4g==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@snazzah/davey-freebsd-x64": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/@snazzah/davey-freebsd-x64/-/davey-freebsd-x64-0.1.12.tgz",
"integrity": "sha512-G1gas5HrC4Xp3mRY0+OeAqXS6fG2tRgBEc8gQh69Hw4YK9RV9mzQKcmoKMkBM72U1+2C+2u57dolnKKzwozByQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@snazzah/davey-linux-arm-gnueabihf": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/@snazzah/davey-linux-arm-gnueabihf/-/davey-linux-arm-gnueabihf-0.1.12.tgz",
"integrity": "sha512-97Fujh82r2Ll7dPZeNuoZ3yKfsqycf3c93OWXOo/ThNL/18Onl2Ht4SIvpX6VHhfeS9bDpHJ1lHCaz1b/i5ocw==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@snazzah/davey-linux-arm64-gnu": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/@snazzah/davey-linux-arm64-gnu/-/davey-linux-arm64-gnu-0.1.12.tgz",
"integrity": "sha512-FWyAOv52cHKDM4BOsmImKKogHFvqNFoXmZcicNJbX3XpVl8Mas88ZoXQ+IA5V+qc9pNtAl1MbWwEZ9JrqAQtbg==",
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@snazzah/davey-linux-arm64-musl": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/@snazzah/davey-linux-arm64-musl/-/davey-linux-arm64-musl-0.1.12.tgz",
"integrity": "sha512-ptRbLSQxtV6EjXppS5z7qaPDI0NRKhrkJYsTlAjEghmOvlAObozSCYYnMO6nbkt6Ab3+lWqyahClzcRNcD2ouw==",
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@snazzah/davey-linux-x64-gnu": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/@snazzah/davey-linux-x64-gnu/-/davey-linux-x64-gnu-0.1.12.tgz",
"integrity": "sha512-w86fZvhJn0ErOoAQHt2UbQ95V/cgwvfvQ4GlTPQLCzt58nn+rLlXLgPn90qYlSQrZxFW38rKXwqVOMbm9p+pwQ==",
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@snazzah/davey-linux-x64-musl": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/@snazzah/davey-linux-x64-musl/-/davey-linux-x64-musl-0.1.12.tgz",
"integrity": "sha512-LLNnO+hfG41ymeI+O1YHo5/0h3aKaetUNLdkBwpdJsjoyKMXZaeCnB+aHNkkupJCMPmWS0g6iPMCHUOqZSBcTg==",
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@snazzah/davey-wasm32-wasi": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/@snazzah/davey-wasm32-wasi/-/davey-wasm32-wasi-0.1.12.tgz",
"integrity": "sha512-MPKFuqYVkDFXheR7qmtEY4FWxQ/ADfgsCojQWHi13sibUqCTR9q2F1LqNn2i9IVh3sh1sxeg87fdFMCH63pl7g==",
"cpu": [
"wasm32"
],
"license": "MIT",
"optional": true,
"dependencies": {
"@napi-rs/wasm-runtime": "^1.1.5"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@snazzah/davey-win32-arm64-msvc": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/@snazzah/davey-win32-arm64-msvc/-/davey-win32-arm64-msvc-0.1.12.tgz",
"integrity": "sha512-Uf4OYHyfbXpzyaOqIV8/h6kv166Qni5+Bxmc1E/ov4uhhKO8qXbOky8zbVOzu0U4cv5ll3s4IQ8jDAJkx/K75Q==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@snazzah/davey-win32-ia32-msvc": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/@snazzah/davey-win32-ia32-msvc/-/davey-win32-ia32-msvc-0.1.12.tgz",
"integrity": "sha512-nRVbKTsb2ldcPI8D4BDA7P/UeiMEMvR+wYuUMp7H1pRD/3dF2hKo+MzUU+Pn78EhuYA3CWHItiAcS/GsPld67A==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@snazzah/davey-win32-x64-msvc": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/@snazzah/davey-win32-x64-msvc/-/davey-win32-x64-msvc-0.1.12.tgz",
"integrity": "sha512-AgUA3itPDVkxQq7RIkgE1thCiWePwWjyfOZefBBxGIlMWpRUOSwF4vY9kNLnrScyJcFULdR+Zm8PwiwV8/RKnw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@szmarczak/http-timer": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz",
@@ -1075,6 +1378,16 @@
"node": ">=14.16"
}
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
"integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@types/http-cache-semantics": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
+2 -2
View File
@@ -7,14 +7,14 @@
"type": "module",
"main": "src/index.js",
"engines": {
"node": ">=20"
"node": ">=22.12.0"
},
"scripts": {
"start": "node src/index.js",
"dev": "node --watch src/index.js"
},
"dependencies": {
"@discordjs/voice": "^0.18.0",
"@discordjs/voice": "^0.19.2",
"@fastify/cookie": "^11.1.2",
"@fastify/multipart": "^10.1.0",
"@fastify/static": "^10.1.2",
+24 -2
View File
@@ -203,6 +203,7 @@ const NETZSCHRITT = ['Websocket auf', 'Anmeldung', 'UDP-Handschlag', 'Protokollw
const ANMELDUNG = 1;
const UDP_HANDSCHLAG = 2;
const PROTOKOLLWAHL = 3;
const ZU = 6;
// Damit dasselbe Netzteil nicht zweimal beobachtet wird. Bei einem Rückfall
// baut die Bibliothek ein neues — das soll durchaus wieder mitgeschrieben
@@ -257,10 +258,27 @@ export function beitrittsDiagnose(schritte) {
// 19302 antwortet, TCP 2096 zum Sprachserver offen). Die Behauptung war
// also falsch. Jetzt entscheidet der Mitschnitt statt einer Vermutung.
const netz = schritte.netz ?? [];
const letzterSchritt = netz.filter((e) => e.schritt !== undefined).at(-1)?.schritt;
const schluss = netz.filter((e) => e.schliessung !== undefined).at(-1)?.schliessung;
// Der letzte *bedeutsame* Schritt. „zu" steht immer am Ende, weil das
// Netzteil sich erst schliesst und dann den Code meldet — wer den nimmt,
// kommt nie bei einer Ursache an. Genau das ist am 14.08.2026 passiert:
// der Mitschnitt hatte die Antwort, die Auswertung hat sie verworfen.
const letzterSchritt = netz
.filter((e) => e.schritt !== undefined && e.schritt !== ZU)
.at(-1)?.schritt;
const zu = schluss === undefined ? 'ohne Schliesscode' : `Schliesscode ${schluss}`;
// 4017 ist eindeutig, unabhaengig davon, wie weit es kam: Discord verlangt
// seit dem 01.03.2026 DAVE (Ende-zu-Ende-Verschluesselung) in allen
// Nicht-Stage-Sprachkanaelen und weist aeltere Gegenstellen sofort ab.
if (schluss === 4017) {
return `Discord hat die Verbindung zu **${endpunkt}** abgewiesen (4017): Sprachkanäle `
+ 'verlangen seit dem 01.03.2026 **DAVE**, die Ende-zu-Ende-Verschlüsselung. '
+ 'Dafür braucht es `@discordjs/voice` ab 0.19 samt `@snazzah/davey` — ältere '
+ 'Fassungen melden `max_dave_protocol_version: 0` und werden abgewiesen. '
+ 'Stage-Kanäle sind ausgenommen.';
}
if (letzterSchritt === UDP_HANDSCHLAG) {
// `performIPDiscovery` hat in 0.18 keine Zeitschranke und die
// UDP-Keepalives werden nicht mehr ausgewertet — bleibt die Antwort
@@ -345,7 +363,11 @@ export async function spielen(kanal, station) {
netzteil[BEOBACHTET] = true;
schritte.netz.push({ schritt: netzteil.state.code });
netzteil.on('stateChange', (_, neu) => {
if (schritte.netz.at(-1)?.schritt !== neu.code) schritte.netz.push({ schritt: neu.code });
// Gegen den letzten *Schritt* vergleichen, nicht gegen den
// letzten Eintrag: sonst steht "zu" zweimal da, einmal vor und
// einmal nach dem Schliesscode.
const vorher = schritte.netz.filter((e) => e.schritt !== undefined).at(-1)?.schritt;
if (vorher !== neu.code) schritte.netz.push({ schritt: neu.code });
});
// Den Schliesscode wirft die Bibliothek weg: ausser bei 4014 geht
// sie damit stumm zurück nach „signalling". Hier bleibt er stehen.