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