Add encrypted managed UDP relay and native voice conformance
This commit is contained in:
+32
-2
@@ -188,8 +188,38 @@ Success returns permissions, then a cloned snapshot; peers receive joined/update
|
||||
events. Server-authoritative text replaces supplied sender ids/timestamps, limits
|
||||
bodies to 4096 UTF-8 bytes, and acknowledges valid or rejected routing. Channel text
|
||||
requires membership; private text echoes to sender and recipient. Protected channel
|
||||
joins and all admin/moderation handlers are pending. No UDP port or media features
|
||||
are advertised; voice subscription explicitly fails until the SFU is implemented.
|
||||
joins and all admin/moderation handlers are pending.
|
||||
|
||||
`VoiceServer.MediaEndPoint` exposes the bound UDP endpoint; UDP uses the same address
|
||||
and port number as TCP, and `ServerHello.udp_port` advertises it. Successful authentication
|
||||
issues a 16-byte binding token. TLS confirmation echoes an acknowledgement; a protocol-v2
|
||||
bootstrap packet binds the first UDP endpoint. Tokens cannot replace an established
|
||||
endpoint; reconnect to change endpoints. Invalid tokens and malformed packets are ignored.
|
||||
|
||||
Voice subscription, unsubscribe, stream announce/stop and stream-state signaling are
|
||||
implemented. Announces require subscription and support microphone, screen audio and
|
||||
auxiliary device streams, with at most 16 streams per user and labels up to 128 characters.
|
||||
Stream ids are monotonically assigned per user; SSRCs are assigned server-wide.
|
||||
Channel audio settings are authoritative; requested bitrate may lower the channel ceiling
|
||||
(nonzero requests below 500 bps fail). User updates include the actor. Stream-state updates
|
||||
use the authenticated sender id and ignore unknown stream ids.
|
||||
|
||||
Channel movement clears active streams; joining the current channel preserves them.
|
||||
Unsubscribe clears streams. Disconnect removes routing and retires media resources,
|
||||
even if no UDP traffic follows. Senders must own the SSRC and be subscribed; recipients
|
||||
must be subscribed, bound, in the same channel and not deafened. Server-muted senders
|
||||
cannot relay. Every voice packet is authenticated with the sender's directional key;
|
||||
the SFU reseals encoded bytes for each recipient without decoding, replacing only the
|
||||
sequence and ciphertext/tag. Replay rejection precedes authentication; successful
|
||||
authentication advances the replay window.
|
||||
|
||||
The UDP loop exclusively owns media crypto, endpoint mutation and packet buffers.
|
||||
Control handlers publish immutable routing snapshots. Crypto is created within the
|
||||
TLS owner loop and transferred once. A coalesced notification wakes retired-key cleanup.
|
||||
The synchronous fan-out core allocates zero managed bytes with platform ChaCha20-Poly1305;
|
||||
socket scheduling and the allocating BouncyCastle fallback are outside that guarantee.
|
||||
UDP keepalives are echoed for bound endpoints. The full media-aware reaper remains pending;
|
||||
the existing 60-second TLS receive-idle timeout still applies.
|
||||
|
||||
`AccountStore(path)` retains the C++ schema version 2, accepts version 1 migration,
|
||||
and rejects unknown revisions. Opening an existing channel table does not reseed it.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
**Status:** wire/media crypto and TLS/exporter foundations implemented under `dotnet/`,
|
||||
including C++ interoperability, persisted TOFU, compatible server credentials,
|
||||
codec/DSP wrappers, desktop native staging, and the initial managed TLS control server.
|
||||
Phase 4 remains in progress; UDP relay, complete session administration, device audio,
|
||||
Phase 4 remains in progress; complete session administration, device audio,
|
||||
managed client state, and UI phases remain planned.
|
||||
See `dotnet/README.md`, `docs/api-dotnet.md`, and `PROGRESS.md` for verification and next steps.
|
||||
**Target runtime:** .NET 10 LTS (in-service to Nov 2028), with .NET 11 as the follow-on.
|
||||
@@ -729,9 +729,24 @@ the existing BouncyCastle dependency with a strict libsodium PHC parser, not a n
|
||||
Konscious dependency. Native database tests prove password compatibility in both
|
||||
directions without resets. SQLite uses `Microsoft.Data.Sqlite.Core` 10.0.5,
|
||||
SQLitePCLRaw bundle 3.0.2 and explicitly pinned SourceGear SQLite 3.50.4.2.
|
||||
See `docs/api-dotnet.md` for limits. UDP voice, streams, protected joins, moderation,
|
||||
admin handlers and production configuration remain pending. The exit criterion
|
||||
below has not yet been met.
|
||||
See `docs/api-dotnet.md` for limits. This first checkpoint did not include UDP voice,
|
||||
streams, protected joins, moderation, admin handlers or production configuration.
|
||||
The subsequent voice checkpoint is described below.
|
||||
|
||||
**Voice checkpoint:** the managed server now advertises UDP, issues session-bound
|
||||
tokens, implements voice subscription and multi-stream signaling, and relays encrypted
|
||||
Opus with recipient-specific counters. The first UDP endpoint is fixed for the session;
|
||||
reconnect for endpoint changes. Immutable routing snapshots separate control handlers
|
||||
from the UDP crypto owner. Real-socket tests cover replay/forgery/SSRC rejection,
|
||||
channel/subscription isolation, stream stop and disconnect. A native client oracle
|
||||
exercises bidirectional microphone and screen audio in mono and stereo. The fan-out
|
||||
core has a 50-subscriber allocation regression test; transport scheduling and the
|
||||
BouncyCastle crypto fallback are excluded from its zero-allocation guarantee.
|
||||
Two real C++ `vccli` processes also pass join/text/bidirectional voice tests using
|
||||
finite `--test-tone-ms` external capture/playback. The transport load test delivers
|
||||
all 2,500 recipient packets from a sender paced at 50 pps to 50 subscribers.
|
||||
Administration, protected joins, production configuration and media-aware reaping
|
||||
still remain before Phase 4 completion.
|
||||
|
||||
1. `VoiceCat.Server`: accept loop, `ConnSession` protocol handling, session registry.
|
||||
2. `Db` on `Microsoft.Data.Sqlite` — same schema. **Resolve the Argon2id hash-compat
|
||||
|
||||
+7
-2
@@ -12,8 +12,13 @@ packaging and TLS/server/client migration remain later checkpoints.
|
||||
- Preserve the existing protobuf and 20-byte media wire formats; keep C++ as the oracle.
|
||||
- **Exit:** managed framing, headers, and ciphertext match fixtures generated by C++;
|
||||
managed tests and the existing C++ behavior suite pass.
|
||||
- **Next:** prove TLS 1.3/exporter interoperability with C++, then port the server before
|
||||
client state/audio/UI migration. Native audio packaging follows with codec/audio work.
|
||||
- **Subsequent checkpoints:** TLS/exporter and credential interoperability, codec/DSP
|
||||
desktop packaging, and managed control/UDP server slices are implemented. Two C++
|
||||
`vccli` processes authenticate, join, chat and exchange mono/stereo voice through
|
||||
the managed server. The 50-subscriber fan-out core has an allocation regression test.
|
||||
- **Next:** finish managed server administration, protected joins, production configuration
|
||||
and media-aware reaping, then audio/client core, Windows cutover, C# AppKit and UIKit.
|
||||
Keep the Swift ReplayKit extension and its shared ring; defer C++ removal until parity.
|
||||
- See `docs/porting-to-dotnet.md` and `dotnet/README.md`.
|
||||
|
||||
Each milestone is shippable/testable on its own. The headless C++ test client (`vccli`)
|
||||
|
||||
+15
-7
@@ -82,7 +82,8 @@ mandatory from the first build. This was chosen over DTLS after weighing two fin
|
||||
`0x01` for server→client. Each export yields a 32-byte directional media key.
|
||||
No second handshake, no certificates on the UDP path — the UDP channel inherits the
|
||||
authenticated, MITM-resistant TLS session's trust.
|
||||
2. Each UDP voice frame is sealed with **ChaCha20-Poly1305** (libsodium, ISC license).
|
||||
2. Each UDP voice frame is sealed with **ChaCha20-Poly1305** (libsodium in C++;
|
||||
platform cryptography with a BouncyCastle fallback in .NET).
|
||||
3. The full 20-byte header is AEAD **associated data**. The server authenticates/decrypts
|
||||
inbound media and reseals for each recipient, replacing the sequence with that
|
||||
recipient's next send counter. It forwards the encoded Opus bytes without decoding audio.
|
||||
@@ -115,14 +116,21 @@ the design depends on that.
|
||||
UDP packets are not individually authenticated to a *user* beyond the transport session.
|
||||
Binding works as:
|
||||
|
||||
1. `AuthResult.udp_token` (issued over TLS) is a short-lived, single-use, random token tied
|
||||
to `session_id`.
|
||||
2. Client's first UDP message is `UdpBinding{udp_token}`, sent as the first AEAD media frame
|
||||
using the keys exported from the TLS session.
|
||||
3. Server validates the token, binds the **5-tuple → session_id**, and discards the token.
|
||||
1. `AuthResult.udp_token` (issued over TLS) is a random 16-byte token tied to the
|
||||
authenticated session. The client confirms it with `UdpBinding` over TLS.
|
||||
2. Protocol v2 bootstraps UDP with a **plaintext** `UDP_BINDING` packet: the 20-byte
|
||||
binary header followed by the token. This is not a protobuf or an AEAD voice frame.
|
||||
3. Server validates the token and binds the **5-tuple → session_id**. The managed server
|
||||
accepts the first endpoint only; further bootstrap packets cannot replace it.
|
||||
Endpoint changes require a new authenticated session. The token remains available
|
||||
for TLS confirmation but cannot establish a second binding. Session removal retires
|
||||
its endpoint, token and directional keys. The C++ oracle currently permits rebinding
|
||||
with the same token; this differs in policy, not in the packet format.
|
||||
4. Thereafter, frames are accepted only on that bound tuple; ssrcs are checked against the
|
||||
streams the session announced. Source-address spoofing can't hijack a session because the
|
||||
attacker lacks the media key and the token.
|
||||
attacker lacks the media key. The bootstrap token is visible on UDP, so it is not
|
||||
a substitute for AEAD authentication and SSRC ownership checks. Header-only keepalives
|
||||
are echoed only for bound endpoints; they provide liveness, not authenticated content.
|
||||
|
||||
## 4. Authentication & accounts (settled: guests + local accounts)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user