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}