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