fix(server): bind UDP media to the TCP port so self-host needs one forward rule
media_port defaulted to 0 (OS-assigned) and --port only set the TCP bind_port, so the UDP relay bound a random high port and advertised it to clients in HELLO. Self-hosters forwarding only 8384/udp saw connect-OK-but-no-voice, contradicting docs/deployment.md (control and media share one port). Media now follows bind_port when media_port is unset; 0=OS-assigned survives when bind_port is also 0 so ephemeral-port tests are unaffected. Banner now reads TCP :8384 UDP :8384. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
16
PROGRESS.md
16
PROGRESS.md
@@ -10,6 +10,22 @@ up instantly. Newest status at the top.
|
|||||||
|
|
||||||
## ▶ Where we left off / next action
|
## ▶ Where we left off / next action
|
||||||
|
|
||||||
|
- **Done (2026-06-22):** **UDP media now shares the TCP port (self-host port-forward fix).** Symptom: a
|
||||||
|
remote self-hosted server (`iamtalon.me:8384`, TCP+UDP 8384 forwarded) accepted TCP connections but
|
||||||
|
passed no voice. Root cause: `Config::media_port` defaulted to `0` = OS-assigned, and `main.cpp`'s
|
||||||
|
`--port` only set `bind_port` (TCP) — so the UDP relay bound a *random high port*, advertised it to
|
||||||
|
clients in HELLO (`udp_port`), and clients sent voice there. With only `8384/udp` forwarded those
|
||||||
|
packets were dropped → connect OK, no audio. This contradicted `docs/deployment.md` ("Control and media
|
||||||
|
share one port number on TCP+UDP"). **Fix (`server/src/server.cpp`):** media follows bind_port when
|
||||||
|
`media_port == 0` — `media_want = cfg_.media_port != 0 ? cfg_.media_port : cfg_.bind_port`. The
|
||||||
|
`0 = OS-assigned` escape hatch survives when `bind_port` is also 0, so tests that bind ephemeral ports
|
||||||
|
are unaffected (kept the logic in server.cpp rather than hardcoding 8384 as the default, which would
|
||||||
|
collide parallel tests on UDP 8384). Banner now reads `TCP :8384 UDP :8384`. Build + `ctest --preset
|
||||||
|
dev` green (24/24); live-verified banner with `--port 8390` → `UDP :8390`. **Action for self-hosters:**
|
||||||
|
redeploy and confirm the startup banner shows matching TCP/UDP ports; the existing single forward rule
|
||||||
|
is now correct. If voice still fails, watch the server's rate-limited `[media] dropped frames —
|
||||||
|
unmapped-endpoint=…` line (NAT source-port rewrite would be the next suspect).
|
||||||
|
|
||||||
- **Awaiting on-device verification (2026-06-22):** **iOS real echo cancellation / noise suppression
|
- **Awaiting on-device verification (2026-06-22):** **iOS real echo cancellation / noise suppression
|
||||||
via native VPIO.** Root cause of "voice chat doesn't sound like a call" (echo + no NR): real iOS
|
via native VPIO.** Root cause of "voice chat doesn't sound like a call" (echo + no NR): real iOS
|
||||||
AEC/NS/AGC come only from Apple's Voice-Processing I/O unit (VPIO), but the core uses miniaudio's
|
AEC/NS/AGC come only from Apple's Voice-Processing I/O unit (VPIO), but the core uses miniaudio's
|
||||||
|
|||||||
@@ -69,8 +69,12 @@ int Server::run() {
|
|||||||
|
|
||||||
// ── UDP media relay (M2) ─────────────────────────────────────────────────
|
// ── UDP media relay (M2) ─────────────────────────────────────────────────
|
||||||
auto media_relay = std::make_shared<MediaRelay>(io, registry);
|
auto media_relay = std::make_shared<MediaRelay>(io, registry);
|
||||||
if (!media_relay->bind(cfg_.media_port)) {
|
// Control and media share one port number on TCP+UDP (docs/deployment.md): when media_port
|
||||||
std::fprintf(stderr, "[server] failed to bind UDP media port %u\n", cfg_.media_port);
|
// is left at 0, follow bind_port so a single forward rule covers both. If bind_port is also 0
|
||||||
|
// (tests), this stays 0 and the OS picks the UDP port (reported via on_media_ready).
|
||||||
|
uint16_t media_want = cfg_.media_port != 0 ? cfg_.media_port : cfg_.bind_port;
|
||||||
|
if (!media_relay->bind(media_want)) {
|
||||||
|
std::fprintf(stderr, "[server] failed to bind UDP media port %u\n", media_want);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
media_relay->start();
|
media_relay->start();
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ struct Config {
|
|||||||
std::string server_name = "VoiceCat Server";
|
std::string server_name = "VoiceCat Server";
|
||||||
std::string data_dir = "voicecat-data";
|
std::string data_dir = "voicecat-data";
|
||||||
uint16_t bind_port = 8384; // 0 = let OS pick (useful for tests)
|
uint16_t bind_port = 8384; // 0 = let OS pick (useful for tests)
|
||||||
uint16_t media_port = 0; // M2 UDP media port; 0 = OS-assigned
|
uint16_t media_port = 0; // M2 UDP media; 0 = follow bind_port (or OS-pick if that's 0)
|
||||||
bool allow_guests = true;
|
bool allow_guests = true;
|
||||||
// Called with the actual bound TCP port once the acceptor is ready.
|
// Called with the actual bound TCP port once the acceptor is ready.
|
||||||
std::function<void(uint16_t)> on_ready;
|
std::function<void(uint16_t)> on_ready;
|
||||||
|
|||||||
Reference in New Issue
Block a user