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