Skip to main content

turn_server/
config.rs

1use std::{collections::HashMap, fs::read_to_string, net::SocketAddr, str::FromStr};
2
3use anyhow::Result;
4use clap::Parser;
5use serde::{Deserialize, Serialize};
6
7use crate::service::{InterfaceAddr, Transport, session::ports::PortRange};
8
9/// SSL configuration
10///
11/// Used by the TCP data-plane interface (`server.interfaces.ssl`), the
12/// management API, the Prometheus exporter, and the hook client. For the data
13/// plane, enabling this on a TCP interface turns it into TLS; the UDP transport
14/// does not support `ssl`.
15#[derive(Deserialize, Serialize, Debug, Clone)]
16#[serde(rename_all = "kebab-case")]
17pub struct Ssl {
18    ///
19    /// SSL private key file
20    ///
21    pub private_key: String,
22    ///
23    /// SSL certificate chain file
24    ///
25    pub certificate_chain: String,
26}
27
28#[derive(Deserialize, Serialize, Debug, Clone)]
29#[serde(tag = "transport", rename_all = "kebab-case")]
30pub enum Interface {
31    Tcp {
32        listen: SocketAddr,
33        ///
34        /// external address
35        ///
36        /// specify the node external address and port.
37        /// for the case of exposing the service to the outside,
38        /// you need to manually specify the server external IP
39        /// address and service listening port.
40        ///
41        external: SocketAddr,
42        ///
43        /// Idle timeout
44        ///
45        /// If no packet is received within the specified number of seconds, the
46        /// connection will be closed to prevent resources from being occupied
47        /// for a long time.
48        #[serde(default = "Interface::idle_timeout")]
49        idle_timeout: u32,
50        ///
51        /// SSL configuration
52        ///
53        /// Enabling this on a TCP interface turns the interface into TLS.
54        /// Only the TCP transport supports `ssl`; the UDP interface does not
55        /// expose this option.
56        ///
57        #[serde(default)]
58        ssl: Option<Ssl>,
59    },
60    Udp {
61        listen: SocketAddr,
62        ///
63        /// external address
64        ///
65        /// specify the node external address and port.
66        /// for the case of exposing the service to the outside,
67        /// you need to manually specify the server external IP
68        /// address and service listening port.
69        ///
70        external: SocketAddr,
71        ///
72        /// Idle timeout
73        ///
74        /// If no packet is received within the specified number of seconds, the
75        /// connection will be closed to prevent resources from being occupied
76        /// for a long time.
77        #[serde(default = "Interface::idle_timeout")]
78        idle_timeout: u32,
79        /// !Deprecated
80        ///
81        /// Maximum Transmission Unit (MTU) size for network packets.
82        ///
83        #[serde(default = "Interface::mtu")]
84        mtu: usize,
85    },
86}
87
88impl Interface {
89    fn mtu() -> usize {
90        1500
91    }
92
93    fn idle_timeout() -> u32 {
94        20
95    }
96}
97
98#[derive(Deserialize, Debug, Clone)]
99#[serde(rename_all = "kebab-case")]
100pub struct Server {
101    ///
102    /// Port range, the maximum range is 65535 - 49152.
103    ///
104    #[serde(default = "Server::port_range")]
105    pub port_range: PortRange,
106    ///
107    /// Maximum number of threads the TURN server can use.
108    ///
109    #[serde(default = "Server::max_threads")]
110    pub max_threads: usize,
111    ///
112    /// turn server realm
113    ///
114    /// specify the domain where the server is located.
115    /// for a single node, this configuration is fixed,
116    /// but each node can be configured as a different domain.
117    /// this is a good idea to divide the nodes by namespace.
118    ///
119    #[serde(default = "Server::realm")]
120    pub realm: String,
121    ///
122    /// turn server listen interfaces
123    ///
124    /// The address and port to which the UDP Server is bound. Multiple
125    /// addresses can be bound at the same time. The binding address supports
126    /// ipv4 and ipv6.
127    ///
128    #[serde(default)]
129    pub interfaces: Vec<Interface>,
130}
131
132impl Server {
133    pub fn get_interface_addrs(&self) -> Vec<InterfaceAddr> {
134        self.interfaces
135            .iter()
136            .map(|item| match item {
137                Interface::Tcp {
138                    listen, external, ..
139                } => InterfaceAddr {
140                    addr: *listen,
141                    external: *external,
142                    transport: Transport::Tcp,
143                },
144                Interface::Udp {
145                    listen, external, ..
146                } => InterfaceAddr {
147                    addr: *listen,
148                    external: *external,
149                    transport: Transport::Udp,
150                },
151            })
152            .collect()
153    }
154}
155
156impl Server {
157    fn realm() -> String {
158        "localhost".to_string()
159    }
160
161    fn port_range() -> PortRange {
162        PortRange::default()
163    }
164
165    fn max_threads() -> usize {
166        num_cpus::get()
167    }
168}
169
170impl Default for Server {
171    fn default() -> Self {
172        Self {
173            realm: Self::realm(),
174            interfaces: Default::default(),
175            port_range: Self::port_range(),
176            max_threads: Self::max_threads(),
177        }
178    }
179}
180
181#[derive(Deserialize, Debug, Clone)]
182#[serde(rename_all = "kebab-case")]
183pub struct Hooks {
184    #[serde(default = "Hooks::max_channel_size")]
185    pub max_channel_size: usize,
186    pub endpoint: String,
187    #[serde(default)]
188    pub ssl: Option<Ssl>,
189    #[serde(default = "Hooks::timeout")]
190    pub timeout: u32,
191}
192
193impl Hooks {
194    fn max_channel_size() -> usize {
195        1024
196    }
197
198    fn timeout() -> u32 {
199        5
200    }
201}
202
203#[derive(Deserialize, Debug, Clone)]
204#[serde(rename_all = "kebab-case")]
205pub struct Api {
206    ///
207    /// rpc server listen
208    ///
209    /// This option specifies the rpc server binding address used to control
210    /// the turn server.
211    ///
212    #[serde(default = "Api::bind")]
213    pub listen: SocketAddr,
214    #[serde(default)]
215    pub ssl: Option<Ssl>,
216    #[serde(default = "Api::timeout")]
217    pub timeout: u32,
218}
219
220impl Api {
221    fn bind() -> SocketAddr {
222        "127.0.0.1:3000"
223            .parse()
224            .expect("Invalid default API bind address")
225    }
226
227    fn timeout() -> u32 {
228        5
229    }
230}
231
232impl Default for Api {
233    fn default() -> Self {
234        Self {
235            timeout: Self::timeout(),
236            listen: Self::bind(),
237            ssl: None,
238        }
239    }
240}
241
242#[derive(Deserialize, Debug, Clone)]
243#[serde(rename_all = "kebab-case")]
244pub struct Prometheus {
245    ///
246    /// prometheus server listen
247    ///
248    /// This option specifies the prometheus server binding address used to expose
249    /// the metrics.
250    ///
251    #[serde(default = "Prometheus::bind")]
252    pub listen: SocketAddr,
253    ///
254    /// ssl configuration
255    ///
256    /// This option specifies the ssl configuration for the prometheus server.
257    ///
258    #[serde(default)]
259    pub ssl: Option<Ssl>,
260}
261
262impl Prometheus {
263    fn bind() -> SocketAddr {
264        "127.0.0.1:9184"
265            .parse()
266            .expect("Invalid default Prometheus bind address")
267    }
268}
269
270impl Default for Prometheus {
271    fn default() -> Self {
272        Self {
273            listen: Self::bind(),
274            ssl: None,
275        }
276    }
277}
278
279#[derive(Deserialize, Debug, Clone, Copy)]
280#[serde(rename_all = "lowercase")]
281pub enum LogLevel {
282    Error,
283    Warn,
284    Info,
285    Debug,
286    Trace,
287}
288
289impl FromStr for LogLevel {
290    type Err = String;
291
292    fn from_str(value: &str) -> Result<Self, Self::Err> {
293        Ok(match value {
294            "trace" => Self::Trace,
295            "debug" => Self::Debug,
296            "info" => Self::Info,
297            "warn" => Self::Warn,
298            "error" => Self::Error,
299            _ => return Err(format!("unknown log level: {value}")),
300        })
301    }
302}
303
304impl Default for LogLevel {
305    fn default() -> Self {
306        Self::Info
307    }
308}
309
310impl From<LogLevel> for log::LevelFilter {
311    fn from(val: LogLevel) -> Self {
312        match val {
313            LogLevel::Error => log::LevelFilter::Error,
314            LogLevel::Debug => log::LevelFilter::Debug,
315            LogLevel::Trace => log::LevelFilter::Trace,
316            LogLevel::Warn => log::LevelFilter::Warn,
317            LogLevel::Info => log::LevelFilter::Info,
318        }
319    }
320}
321
322#[derive(Deserialize, Debug, Default, Clone)]
323#[serde(rename_all = "kebab-case")]
324pub struct Log {
325    ///
326    /// log level
327    ///
328    /// An enum representing the available verbosity levels of the logger.
329    ///
330    #[serde(default)]
331    pub level: LogLevel,
332    /// log to stdout
333    ///
334    /// This option can be used to log to stdout.
335    ///
336    #[serde(default = "Log::stdout")]
337    pub stdout: bool,
338    /// log to file directory
339    ///
340    /// This option can be used to log to a file directory.
341    ///
342    #[serde(default)]
343    pub file_directory: Option<String>,
344}
345
346impl Log {
347    fn stdout() -> bool {
348        true
349    }
350}
351
352#[derive(Deserialize, Debug, Default, Clone)]
353#[serde(rename_all = "kebab-case")]
354pub struct Auth {
355    ///
356    /// static user password
357    ///
358    /// This option can be used to specify the static identity authentication
359    /// information used by the turn server for verification.
360    ///
361    /// Note: this is a high-priority authentication method, turn The server will
362    /// try to use static authentication first, and then use external control
363    /// service authentication.
364    ///
365    #[serde(default)]
366    pub static_credentials: HashMap<String, String>,
367    ///
368    /// Static authentication key value (string) that applies only to the TURN
369    /// REST API.
370    ///
371    /// If set, the turn server will not request external services via the HTTP
372    /// Hooks API to obtain the key.
373    ///
374    pub static_auth_secret: Option<String>,
375    #[serde(default)]
376    pub enable_hooks_auth: bool,
377}
378
379#[derive(Deserialize, Debug, Default, Clone)]
380#[serde(rename_all = "kebab-case")]
381pub struct Config {
382    #[serde(default)]
383    pub server: Server,
384    #[serde(default)]
385    pub api: Option<Api>,
386    #[serde(default)]
387    pub prometheus: Option<Prometheus>,
388    #[serde(default)]
389    pub hooks: Option<Hooks>,
390    #[serde(default)]
391    pub log: Log,
392    #[serde(default)]
393    pub auth: Auth,
394}
395
396#[derive(Parser, Debug)]
397#[command(
398    about = env!("CARGO_PKG_DESCRIPTION"),
399    version = env!("CARGO_PKG_VERSION"),
400    author = env!("CARGO_PKG_AUTHORS"),
401)]
402struct Cli {
403    ///
404    /// Specify the configuration file path
405    ///
406    /// Example: turn-server --config /etc/turn-rs/config.toml
407    ///
408    #[arg(long, short)]
409    config: String,
410}
411
412impl Config {
413    ///
414    /// Load configure from config file and command line parameters.
415    ///
416    /// Load command line parameters, if the configuration file path is specified,
417    /// the configuration is read from the configuration file, otherwise the
418    /// default configuration is used.
419    ///
420    pub fn load() -> Result<Self> {
421        Ok(toml::from_str::<Self>(&read_to_string(
422            &Cli::parse().config,
423        )?)?)
424    }
425}