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}