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_externals(&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".parse().unwrap()
201    }
202
203    fn timeout() -> u32 {
204        5
205    }
206}
207
208impl Default for Api {
209    fn default() -> Self {
210        Self {
211            timeout: Self::timeout(),
212            listen: Self::bind(),
213            ssl: None,
214        }
215    }
216}
217
218#[derive(Deserialize, Debug, Clone, Copy)]
219#[serde(rename_all = "lowercase")]
220pub enum LogLevel {
221    Error,
222    Warn,
223    Info,
224    Debug,
225    Trace,
226}
227
228impl FromStr for LogLevel {
229    type Err = String;
230
231    fn from_str(value: &str) -> Result<Self, Self::Err> {
232        Ok(match value {
233            "trace" => Self::Trace,
234            "debug" => Self::Debug,
235            "info" => Self::Info,
236            "warn" => Self::Warn,
237            "error" => Self::Error,
238            _ => return Err(format!("unknown log level: {value}")),
239        })
240    }
241}
242
243impl Default for LogLevel {
244    fn default() -> Self {
245        Self::Info
246    }
247}
248
249impl LogLevel {
250    pub fn as_level(&self) -> log::Level {
251        match *self {
252            Self::Error => log::Level::Error,
253            Self::Debug => log::Level::Debug,
254            Self::Trace => log::Level::Trace,
255            Self::Warn => log::Level::Warn,
256            Self::Info => log::Level::Info,
257        }
258    }
259}
260
261#[derive(Deserialize, Debug, Default, Clone)]
262#[serde(rename_all = "kebab-case")]
263pub struct Log {
264    ///
265    /// log level
266    ///
267    /// An enum representing the available verbosity levels of the logger.
268    ///
269    #[serde(default)]
270    pub level: LogLevel,
271}
272
273#[derive(Deserialize, Debug, Default, Clone)]
274#[serde(rename_all = "kebab-case")]
275pub struct Auth {
276    ///
277    /// static user password
278    ///
279    /// This option can be used to specify the static identity authentication
280    /// information used by the turn server for verification.
281    ///
282    /// Note: this is a high-priority authentication method, turn The server will
283    /// try to use static authentication first, and then use external control
284    /// service authentication.
285    ///
286    #[serde(default)]
287    pub static_credentials: HashMap<String, String>,
288    ///
289    /// Static authentication key value (string) that applies only to the TURN
290    /// REST API.
291    ///
292    /// If set, the turn server will not request external services via the HTTP
293    /// Hooks API to obtain the key.
294    ///
295    pub static_auth_secret: Option<String>,
296    #[serde(default)]
297    pub enable_hooks_auth: bool,
298}
299
300#[derive(Deserialize, Debug, Default, Clone)]
301#[serde(rename_all = "kebab-case")]
302pub struct Config {
303    #[serde(default)]
304    pub server: Server,
305    #[serde(default)]
306    pub api: Option<Api>,
307    #[serde(default)]
308    pub hooks: Option<Hooks>,
309    #[serde(default)]
310    pub log: Log,
311    #[serde(default)]
312    pub auth: Auth,
313}
314
315#[derive(Parser, Debug)]
316#[command(
317    about = env!("CARGO_PKG_DESCRIPTION"),
318    version = env!("CARGO_PKG_VERSION"),
319    author = env!("CARGO_PKG_AUTHORS"),
320)]
321struct Cli {
322    ///
323    /// Specify the configuration file path
324    ///
325    /// Example: turn-server --config /etc/turn-rs/config.toml
326    ///
327    #[arg(long, short)]
328    config: String,
329}
330
331impl Config {
332    ///
333    /// Load configure from config file and command line parameters.
334    ///
335    /// Load command line parameters, if the configuration file path is specified,
336    /// the configuration is read from the configuration file, otherwise the
337    /// default configuration is used.
338    ///
339    pub fn load() -> Result<Self> {
340        Ok(toml::from_str::<Self>(&read_to_string(
341            &Cli::parse().config,
342        )?)?)
343    }
344}