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