mumble_server_runtime_gateway/
serve.rs

1//! Binding the sockets and starting everything.
2//!
3//! The operational shape of the guide (10.2): build the runtime, install the
4//! router, create the initial shards, then serve. Shards created later by a
5//! flavor need nothing from here.
6
7use std::sync::Arc;
8
9use anyhow::{Context, Result};
10use tokio::net::{TcpListener, UdpSocket};
11use tokio_rustls::TlsAcceptor;
12
13use crate::config::GatewayConfig;
14use crate::connection;
15use crate::metrics::VoiceMetrics;
16use crate::router::ConnectionRouter;
17use crate::runtime::{Runtime, RuntimeHandle};
18use crate::tls::{self, Identity};
19use crate::voice::VoicePlane;
20
21/// A bound gateway, ready to serve.
22///
23/// Built before it runs so a composition binary can create its shards - and a
24/// test can learn the port the operating system picked - between binding and
25/// serving.
26pub struct Gateway {
27    runtime: Runtime,
28    config: Arc<GatewayConfig>,
29    listener: TcpListener,
30    udp: Arc<UdpSocket>,
31    acceptor: TlsAcceptor,
32    voice_metrics: Arc<VoiceMetrics>,
33}
34
35impl Gateway {
36    /// Bind both planes and start the runtime.
37    ///
38    /// # Errors
39    ///
40    /// When either socket cannot be bound, or the TLS identity is unusable.
41    pub async fn bind(config: GatewayConfig, identity: Identity) -> Result<Gateway> {
42        tls::install_crypto_provider();
43        let acceptor = TlsAcceptor::from(tls::server_config(identity)?);
44        let (listener, udp) = bind_both(config.bind).await?;
45
46        let mut config = config;
47        config.bind = listener.local_addr().context("TCP local address")?;
48
49        Ok(Gateway {
50            runtime: Runtime::start(),
51            config: Arc::new(config),
52            listener,
53            udp: Arc::new(udp),
54            acceptor,
55            voice_metrics: Arc::new(VoiceMetrics::default()),
56        })
57    }
58
59    /// The address both planes ended up on.
60    #[must_use]
61    pub fn address(&self) -> std::net::SocketAddr {
62        self.config.bind
63    }
64
65    #[must_use]
66    pub fn runtime(&self) -> RuntimeHandle {
67        self.runtime.handle()
68    }
69
70    #[must_use]
71    pub fn voice_metrics(&self) -> Arc<VoiceMetrics> {
72        Arc::clone(&self.voice_metrics)
73    }
74
75    /// Accept connections until the listener fails.
76    ///
77    /// # Errors
78    ///
79    /// Only when the listener itself fails: one refused connection never ends
80    /// the gateway.
81    pub async fn serve<R: ConnectionRouter>(self, router: R) -> Result<()> {
82        let router = Arc::new(router);
83        let runtime = self.runtime.handle();
84        let voice = Arc::new(VoicePlane::new_with_metrics(
85            Arc::clone(&self.udp),
86            Arc::clone(runtime.peers()),
87            (*self.config).clone(),
88            Arc::clone(&self.voice_metrics),
89        ));
90
91        // The voice plane is owned by this future rather than detached: when
92        // `serve` ends, the plane ends with it.
93        let plane = {
94            let voice = Arc::clone(&voice);
95            tokio::spawn(async move {
96                if let Err(error) = voice.run().await {
97                    eprintln!("mumble-server-runtime-gateway: the voice plane stopped: {error}");
98                }
99            })
100        };
101
102        let accepting = async {
103            loop {
104                let (tcp, from) = self.listener.accept().await.context("TCP accept")?;
105                let acceptor = self.acceptor.clone();
106                let runtime = runtime.clone();
107                let router = Arc::clone(&router);
108                let voice = Arc::clone(&voice);
109                let udp = Arc::clone(&self.udp);
110                let config = Arc::clone(&self.config);
111
112                // One task per connection, and it owns the socket outright.
113                // Detached on purpose: a connection outlives nothing but itself,
114                // and its cleanup is in its own tail rather than in a joiner.
115                tokio::spawn(async move {
116                    if let Err(error) =
117                        connection::serve(tcp, acceptor, runtime, router, voice, udp, config).await
118                    {
119                        eprintln!(
120                            "mumble-server-runtime-gateway: connection from {from} ended: {error:#}"
121                        );
122                    }
123                });
124            }
125        };
126
127        let outcome: Result<()> = accepting.await;
128        plane.abort();
129        outcome
130    }
131}
132
133/// How many times an ephemeral bind retries before giving up.
134///
135/// Only ever reached when the operating system hands out a TCP port whose UDP
136/// twin is already taken, which is uncommon and independent between attempts.
137const EPHEMERAL_ATTEMPTS: u32 = 16;
138
139/// Bind the control plane and the voice plane on the same port.
140///
141/// Mumble uses one number for both, so the two binds have to agree. With an
142/// explicit port that is one call each. With an ephemeral port (`:0`) it is a
143/// race: the kernel picks a free **TCP** port, which says nothing about UDP, and
144/// binding the pair can genuinely fail. Retrying is the honest answer - each
145/// attempt draws an independent number - and it is bounded so a machine with no
146/// free pair reports that instead of spinning.
147async fn bind_both(address: std::net::SocketAddr) -> Result<(TcpListener, UdpSocket)> {
148    let mut attempts = if address.port() == 0 {
149        EPHEMERAL_ATTEMPTS
150    } else {
151        1
152    };
153
154    loop {
155        attempts = attempts.saturating_sub(1);
156        let listener = TcpListener::bind(address)
157            .await
158            .with_context(|| format!("binding TCP {address}"))?;
159        let bound = listener.local_addr().context("TCP local address")?;
160
161        match UdpSocket::bind(bound).await {
162            Ok(udp) => return Ok((listener, udp)),
163            Err(error) if attempts > 0 => {
164                // Dropping the listener releases the TCP port, so the next
165                // attempt is free to draw a different one.
166                drop(listener);
167                eprintln!(
168                    "mumble-server-runtime-gateway: UDP {bound} was taken ({error}), trying another port"
169                );
170            }
171            Err(error) => {
172                return Err(anyhow::Error::from(error))
173                    .with_context(|| format!("binding UDP {bound}"));
174            }
175        }
176    }
177}
178
179/// Bind, create shards, and serve, in one call.
180///
181/// The shape most composition binaries want. `build` runs after the sockets are
182/// bound and before the first connection is accepted, which is the only window
183/// in which "the initial shards exist before anyone can be routed to one" is
184/// guaranteed.
185///
186/// # Errors
187///
188/// Whatever [`Gateway::bind`] or [`Gateway::serve`] report.
189pub async fn serve<R: ConnectionRouter>(
190    config: GatewayConfig,
191    identity: Identity,
192    build: impl FnOnce(&RuntimeHandle) -> R,
193) -> Result<()> {
194    let gateway = Gateway::bind(config, identity).await?;
195    let router = build(&gateway.runtime());
196    gateway.serve(router).await
197}