mumble_server_runtime_gateway/voice.rs
1//! The UDP voice plane. **No shard task takes part in it.**
2//!
3//! ```text
4//! 1. recv_from(addr)
5//! 2. peers.by_address(addr) -> one hash; absent means the cold path
6//! 3. peer.decrypt(datagram) -> this connection's OCB2 domain alone
7//! 4. peer.routing() -> the shard's table, an Arc read
8//! 5. routing.receivers(sender) -> a borrowed slice, no allocation
9//! 6. per receiver, if the cursor gate passes: encrypt with ITS key,
10//! then send_to, or push onto its queue for the TCP tunnel
11//! ```
12//!
13//! The gate at step 6 is the whole of the audio ordering protocol:
14//!
15//! ```text
16//! receiver.cursor >= routing.since(sender)
17//! ```
18//!
19//! One atomic load and one comparison. It is conservative on purpose - a lagging
20//! receiver loses audio from speakers it already knew about, at worst a few
21//! hundred milliseconds of silence for a client already in trouble - and it
22//! replaces every handshake a "is the view ready" protocol would have needed.
23//!
24//! Revocation needs no gate at all: the shard publishes its table before it
25//! pushes any view, and a route that is gone is simply absent. Cutting too early
26//! is always safe; you hear less than your due, never more.
27//!
28//! REF: docs/design/guide-implementation.md 9.5, 9.7
29
30use std::net::SocketAddr;
31use std::sync::Arc;
32use std::time::Instant;
33
34use anyhow::{Context, Result};
35use mumble_server_runtime_crypto::{BLOCK_SIZE, CryptState, KEY_SIZE};
36use mumble_server_runtime_protocol::messages::{tcp, udp};
37use mumble_server_runtime_protocol::{ControlMessage, UdpMessage, decode_udp, encode_udp};
38use mumble_server_runtime_shard::VoiceAdmission;
39use ring::rand::{SecureRandom, SystemRandom};
40use tokio::net::UdpSocket;
41
42use crate::config::GatewayConfig;
43use crate::limits;
44use crate::peer::{Peer, Peers};
45
46/// A sealed datagram and where it goes.
47pub type Datagram = (Vec<u8>, SocketAddr);
48
49/// The wire value of the server loopback target.
50///
51/// REF: references/vendored/MumbleUDP.proto : `Audio.target` - "2^5-1 = 31" is
52/// documented as "server loopback".
53const LOOPBACK_TARGET: u32 = 31;
54
55/// Normal speech, client to server.
56const NORMAL_TARGET: u32 = 0;
57
58/// Normal speech, server to client.
59///
60/// REF: references/vendored/MumbleUDP.proto : `Audio.context` - "0: Normal
61/// speech, 1: Shout to channel, 2: Whisper to user".
62const NORMAL_CONTEXT: u32 = 0;
63
64/// Generate a fresh OCB2 key and the two nonces for a new connection.
65///
66/// REF: references/mumble/src/murmur/Messages.cpp : `msgAuthenticate` sets
67/// `server_nonce = getEncryptIV()` and `client_nonce = getDecryptIV()`, so on
68/// the server the encrypt IV is the server nonce (S2C) and the decrypt IV is
69/// the client nonce (C2S).
70///
71/// # Errors
72///
73/// When the system randomness source fails.
74pub fn generate_crypt_setup(rng: &SystemRandom) -> Result<(tcp::CryptSetup, CryptState)> {
75 let mut key = [0u8; KEY_SIZE];
76 let mut server_nonce = [0u8; BLOCK_SIZE];
77 let mut client_nonce = [0u8; BLOCK_SIZE];
78 rng.fill(&mut key)
79 .map_err(|_unspecified| anyhow::anyhow!("rng failed for the OCB2 key"))?;
80 rng.fill(&mut server_nonce)
81 .map_err(|_unspecified| anyhow::anyhow!("rng failed for the server nonce"))?;
82 rng.fill(&mut client_nonce)
83 .map_err(|_unspecified| anyhow::anyhow!("rng failed for the client nonce"))?;
84
85 let state = CryptState::new(&key, &server_nonce, &client_nonce);
86 let setup = tcp::CryptSetup {
87 key: Some(key.to_vec()),
88 server_nonce: Some(server_nonce.to_vec()),
89 client_nonce: Some(client_nonce.to_vec()),
90 };
91 Ok((setup, state))
92}
93
94/// The UDP plane.
95pub struct VoicePlane {
96 socket: Arc<UdpSocket>,
97 peers: Arc<Peers>,
98 config: GatewayConfig,
99}
100
101impl VoicePlane {
102 #[must_use]
103 pub fn new(socket: Arc<UdpSocket>, peers: Arc<Peers>, config: GatewayConfig) -> VoicePlane {
104 VoicePlane {
105 socket,
106 peers,
107 config,
108 }
109 }
110
111 /// Service datagrams until the socket errors. Runs for the life of the
112 /// gateway.
113 ///
114 /// # Errors
115 ///
116 /// Only when the socket itself fails. One bad datagram never ends the plane.
117 pub async fn run(&self) -> Result<()> {
118 // Larger reads would be truncated by `recv_from` anyway, and the size
119 // band refuses anything near this.
120 let mut buffer = vec![0u8; 2048];
121 loop {
122 let (len, from) = self
123 .socket
124 .recv_from(&mut buffer)
125 .await
126 .context("UDP recv_from")?;
127 let datagram = buffer.get(..len).unwrap_or_default();
128
129 for (sealed, to) in self.handle_datagram(datagram, from) {
130 if let Err(error) = self.socket.send_to(&sealed, to).await {
131 // One failed send is not fatal to the plane.
132 eprintln!("mumble-server-runtime-gateway: UDP send to {to} failed: {error}");
133 }
134 }
135 }
136 }
137
138 /// Process one datagram. All crypto is synchronous and happens here; the
139 /// caller performs the awaited sends.
140 ///
141 /// Every path ends in an explicit outcome (R6): datagrams to send, a queued
142 /// tunnel message, or a logged drop.
143 pub fn handle_datagram(&self, datagram: &[u8], from: SocketAddr) -> Vec<Datagram> {
144 // Hot path: an address we have already proven.
145 if let Some(peer) = self.peers.by_address(from) {
146 return match peer.decrypt(datagram) {
147 Some(plaintext) => self.dispatch(&peer, &plaintext, from),
148 None => {
149 // Replay, tamper or a desynchronised nonce. Dropping is the
150 // only safe answer; resync handling is future work.
151 eprintln!(
152 "mumble-server-runtime-gateway: undecryptable datagram from bound session {:?} at {from}",
153 peer.session()
154 );
155 Vec::new()
156 }
157 };
158 }
159
160 // Cold path: try the connections whose TCP peer shares this host.
161 // Narrowing by host is what keeps this from being O(connections) for
162 // every stray packet on the internet.
163 for peer in self.peers.candidates(from.ip()) {
164 if let Some(plaintext) = peer.decrypt(datagram) {
165 self.peers.bind(from, &peer);
166 return self.dispatch(&peer, &plaintext, from);
167 }
168 }
169
170 // Not encrypted for anyone: it may be an unencrypted connectivity ping,
171 // which the real server answers before any association exists.
172 if let Ok(UdpMessage::Ping(ping)) = decode_udp(datagram) {
173 return vec![(self.ping_reply(&ping), from)];
174 }
175
176 eprintln!("mumble-server-runtime-gateway: dropping an unroutable datagram from {from}");
177 Vec::new()
178 }
179
180 /// Decode a decrypted packet and act on it.
181 fn dispatch(&self, peer: &Arc<Peer>, plaintext: &[u8], from: SocketAddr) -> Vec<Datagram> {
182 // The band applies to the decoded Mumble packet, so both ingress paths
183 // enforce one rule (spec 15.7).
184 if !limits::is_acceptable_size(plaintext.len()) {
185 eprintln!(
186 "mumble-server-runtime-gateway: dropping a {}-byte packet from session {:?} (outside the band)",
187 plaintext.len(),
188 peer.session()
189 );
190 return Vec::new();
191 }
192
193 match decode_udp(plaintext) {
194 Ok(UdpMessage::Ping(ping)) => {
195 let reply = self.ping_reply(&ping);
196 peer.encrypt(&reply)
197 .map(|sealed| vec![(sealed, from)])
198 .unwrap_or_default()
199 }
200 Ok(UdpMessage::Audio(audio)) => {
201 // This peer is reachable over UDP again, so its own audio goes
202 // back out that way.
203 peer.set_udp_mode(true);
204 self.route(peer, &audio, Instant::now(), plaintext.len())
205 }
206 Err(error) => {
207 eprintln!(
208 "mumble-server-runtime-gateway: bad envelope from session {:?}: {error}",
209 peer.session()
210 );
211 Vec::new()
212 }
213 }
214 }
215
216 /// Route one voice packet from `sender`, whichever transport it arrived on.
217 ///
218 /// Shared by the UDP plane and the TCP tunnel so both apply the same budget,
219 /// the same target vocabulary and the same table.
220 ///
221 /// `bytes` is the decoded packet as it arrived, billed to this connection's
222 /// throughput window whichever transport carried it.
223 pub fn route(
224 &self,
225 sender: &Arc<Peer>,
226 audio: &udp::Audio,
227 now: Instant,
228 bytes: usize,
229 ) -> Vec<Datagram> {
230 if !sender.allow_voice(now, bytes) {
231 eprintln!(
232 "mumble-server-runtime-gateway: session {:?}: voice packet dropped, budget exhausted",
233 sender.session()
234 );
235 return Vec::new();
236 }
237
238 let Some(udp::audio::Header::Target(target)) = audio.header else {
239 // `context` is the server-to-client direction and a header-less
240 // packet says nothing. Either way there is no intent to honour.
241 eprintln!(
242 "mumble-server-runtime-gateway: session {:?}: voice packet with no target",
243 sender.session()
244 );
245 return Vec::new();
246 };
247
248 match target {
249 LOOPBACK_TARGET => self.reflect(sender, audio),
250 NORMAL_TARGET => self.speak(sender, audio),
251 registered => {
252 // Shout and whisper targets are registered with a `VoiceTarget`
253 // control message, which this build refuses. Routing them as
254 // normal speech would deliver voice to listeners the client
255 // never addressed here.
256 eprintln!(
257 "mumble-server-runtime-gateway: session {:?}: refusing unregistered voice target {registered}",
258 sender.session()
259 );
260 Vec::new()
261 }
262 }
263 }
264
265 /// Normal speech: everyone the shard's table says may hear this sender, and
266 /// whose view is far enough along to make sense of it.
267 fn speak(&self, sender: &Arc<Peer>, audio: &udp::Audio) -> Vec<Datagram> {
268 // Read once for the whole packet, so every receiver is decided against
269 // the same table: a change mid-loop cannot deliver half a packet under
270 // one policy and half under the next.
271 let routing = sender.routing();
272 let session = sender.session();
273 let since = routing.since(session).unwrap_or(0);
274
275 let mut datagrams = Vec::new();
276 for receiver in routing.receivers(session) {
277 let Some(peer) = self.peers.by_session(*receiver) else {
278 // In the table but no longer connected: it disconnected between
279 // the shard's last render and this packet.
280 continue;
281 };
282 if peer.cursor() < since {
283 // It has not been told this speaker exists. The client would
284 // discard the audio anyway, so sending it would only waste an
285 // encryption.
286 continue;
287 }
288 self.deliver(&peer, audio, session, &mut datagrams);
289 }
290 datagrams
291 }
292
293 /// The client asked the server to send its own voice back.
294 ///
295 /// Not a route: the audio relation deliberately never contains a self edge,
296 /// because echoing a speaker back to itself is the classic doubled-voice
297 /// bug. This is a separate mechanism the client explicitly asks for, and it
298 /// stays because it is how a human checks a fresh deployment end to end.
299 ///
300 /// Going through no route means it has to ask the mute question itself.
301 /// Deafness is deliberately not asked: the reference server tests it when
302 /// adding a *receiver*, and the loopback skips that path entirely, so a
303 /// deafened speaker still hears its own echo.
304 ///
305 /// REF: references/mumble/src/murmur/Server.cpp : `processMsg` returns on
306 /// `bMute || bSuppress || bSelfMute` before it reaches the
307 /// `SERVER_LOOPBACK` branch.
308 /// REF: references/mumble/src/murmur/AudioReceiverBuffer.cpp : the loopback
309 /// goes through `forceAddReceiver`, which does not test `bDeaf`.
310 fn reflect(&self, sender: &Arc<Peer>, audio: &udp::Audio) -> Vec<Datagram> {
311 let session = sender.session();
312 if !sender.routing().may_speak(session) {
313 eprintln!(
314 "mumble-server-runtime-gateway: session {session:?}: refusing loopback, this speaker is muted"
315 );
316 return Vec::new();
317 }
318
319 let mut datagrams = Vec::new();
320 self.deliver(sender, audio, session, &mut datagrams);
321 datagrams
322 }
323
324 /// Seal one packet for one receiver, on whichever transport that receiver
325 /// last used.
326 ///
327 /// Cross-transport delivery needs no special case: the sender's transport
328 /// never enters into it.
329 fn deliver(
330 &self,
331 receiver: &Arc<Peer>,
332 audio: &udp::Audio,
333 sender: mumble_server_runtime_shard::SessionId,
334 out: &mut Vec<Datagram>,
335 ) {
336 let plaintext = encode_udp(&UdpMessage::Audio(outgoing(audio, sender)));
337
338 match receiver.destination() {
339 Some(address) => match receiver.encrypt(&plaintext) {
340 Some(sealed) => out.push((sealed, address)),
341 None => eprintln!(
342 "mumble-server-runtime-gateway: dropping audio for session {:?}: no usable crypto state",
343 receiver.session()
344 ),
345 },
346 None => match receiver
347 .queue()
348 .push_voice(ControlMessage::UdpTunnel(plaintext))
349 {
350 VoiceAdmission::Accepted => {}
351 // A gap is the right outcome for a receiver already behind:
352 // stale voice helps nobody, and refusing it here keeps the same
353 // connection healthy for the control traffic that still matters.
354 VoiceAdmission::Dropped => {}
355 },
356 }
357 }
358
359 /// A `Ping` reply echoing the client's timestamp plus server statistics.
360 fn ping_reply(&self, request: &udp::Ping) -> Vec<u8> {
361 encode_udp(&UdpMessage::Ping(udp::Ping {
362 timestamp: request.timestamp,
363 request_extended_information: false,
364 server_version_v2: self.config.version_v2(),
365 user_count: u32::try_from(self.peers.len()).unwrap_or(u32::MAX),
366 max_user_count: self.config.max_users,
367 max_bandwidth_per_user: self.config.max_bandwidth,
368 }))
369 }
370}
371
372/// Rewrite a client-sent packet into its server-to-client form.
373///
374/// The Opus payload travels untouched: decoding it would only be needed for
375/// mixing, transcoding or content analysis, none of which happen here.
376///
377/// REF: references/vendored/MumbleUDP.proto : the `Header` oneof carries
378/// `target` client-to-server and `context` server-to-client, so the target is
379/// replaced rather than forwarded; `sender_session` "will always be set when
380/// receiving audio from the server".
381fn outgoing(source: &udp::Audio, sender: mumble_server_runtime_shard::SessionId) -> udp::Audio {
382 udp::Audio {
383 header: Some(udp::audio::Header::Context(NORMAL_CONTEXT)),
384 sender_session: sender.0,
385 frame_number: source.frame_number,
386 opus_data: source.opus_data.clone(),
387 // Positional audio is a flavor concern this build does not express, and
388 // forwarding coordinates a flavor never authorised would leak position.
389 positional_data: Vec::new(),
390 volume_adjustment: 0.0,
391 is_terminator: source.is_terminator,
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 #![allow(clippy::expect_used)]
398
399 use super::*;
400
401 #[test]
402 fn the_outgoing_envelope_replaces_the_target_with_a_context() {
403 let source = udp::Audio {
404 header: Some(udp::audio::Header::Target(0)),
405 sender_session: 0,
406 frame_number: 42,
407 opus_data: vec![1, 2, 3],
408 positional_data: vec![1.0, 2.0, 3.0],
409 volume_adjustment: 0.0,
410 is_terminator: false,
411 };
412
413 let rewritten = outgoing(&source, mumble_server_runtime_shard::SessionId(9));
414
415 assert_eq!(
416 rewritten.header,
417 Some(udp::audio::Header::Context(NORMAL_CONTEXT))
418 );
419 assert_eq!(rewritten.sender_session, 9);
420 assert_eq!(rewritten.frame_number, 42);
421 assert_eq!(
422 rewritten.opus_data, source.opus_data,
423 "routing must never decode Opus"
424 );
425 assert!(
426 rewritten.positional_data.is_empty(),
427 "coordinates no flavor authorised must not travel"
428 );
429 }
430
431 #[test]
432 fn a_generated_crypt_setup_round_trips_through_its_own_state() {
433 let rng = SystemRandom::new();
434 let (setup, mut server) = generate_crypt_setup(&rng).expect("randomness");
435
436 let key: [u8; KEY_SIZE] = setup
437 .key
438 .expect("a key")
439 .try_into()
440 .expect("the right length");
441 let server_nonce: [u8; BLOCK_SIZE] = setup
442 .server_nonce
443 .expect("a nonce")
444 .try_into()
445 .expect("the right length");
446 let client_nonce: [u8; BLOCK_SIZE] = setup
447 .client_nonce
448 .expect("a nonce")
449 .try_into()
450 .expect("the right length");
451
452 // The client mirrors the two nonces: what the server encrypts with, the
453 // client decrypts with.
454 let mut client = CryptState::new(&key, &client_nonce, &server_nonce);
455 let sealed = server.encrypt(b"a voice packet").expect("encryptable");
456
457 assert_eq!(
458 client.decrypt(&sealed).as_deref(),
459 Some(&b"a voice packet"[..])
460 );
461 }
462}