turn_server/
observer.rs

1use std::sync::Arc;
2
3use crate::{config::Config, statistics::Statistics, turn::SessionAddr};
4
5use anyhow::Result;
6use base64::{Engine, prelude::BASE64_STANDARD};
7
8#[cfg(feature = "api")]
9use serde_json::json;
10
11#[derive(Clone)]
12pub struct Observer {
13    config: Arc<Config>,
14    #[cfg(feature = "api")]
15    statistics: Statistics,
16}
17
18impl Observer {
19    #[allow(unused_variables)]
20    pub async fn new(config: Arc<Config>, statistics: Statistics) -> Result<Self> {
21        Ok(Self {
22            #[cfg(feature = "api")]
23            statistics,
24            config,
25        })
26    }
27}
28
29impl crate::turn::Observer for Observer {
30    fn get_password(&self, username: &str) -> Option<String> {
31        // Match the static authentication information first.
32        if let Some(it) = self.config.auth.static_credentials.get(username) {
33            return Some(it.clone());
34        }
35
36        // Try again to match the static authentication key.
37        if let Some(it) = &self.config.auth.static_auth_secret {
38            // Because (TURN REST api) this RFC does not mandate the format of the username,
39            // only suggested values. In principle, the RFC also indicates that the
40            // timestamp part of username can be set at will, so the timestamp is not
41            // verified here, and the external web service guarantees its security by
42            // itself.
43            //
44            // https://datatracker.ietf.org/doc/html/draft-uberti-behave-turn-rest-00#section-2.2
45            return Some(
46                BASE64_STANDARD.encode(
47                    crate::stun::util::hmac_sha1(it.as_bytes(), &[username.as_bytes()])
48                        .ok()?
49                        .into_bytes()
50                        .as_slice(),
51                ),
52            );
53        }
54
55        None
56    }
57
58    /// allocate request
59    ///
60    /// [rfc8489](https://tools.ietf.org/html/rfc8489)
61    ///
62    /// In all cases, the server SHOULD only allocate ports from the range
63    /// 49152 - 65535 (the Dynamic and/or Private Port range [PORT-NUMBERS]),
64    /// unless the TURN server application knows, through some means not
65    /// specified here, that other applications running on the same host as
66    /// the TURN server application will not be impacted by allocating ports
67    /// outside this range.  This condition can often be satisfied by running
68    /// the TURN server application on a dedicated machine and/or by
69    /// arranging that any other applications on the machine allocate ports
70    /// before the TURN server application starts.  In any case, the TURN
71    /// server SHOULD NOT allocate ports in the range 0 - 1023 (the Well-
72    /// Known Port range) to discourage clients from using TURN to run
73    /// standard services.
74    #[allow(clippy::let_underscore_future)]
75    fn allocated(&self, addr: &SessionAddr, name: &str, port: u16) {
76        log::info!(
77            "allocate: address={:?}, interface={:?}, username={:?}, port={}",
78            addr.address,
79            addr.interface,
80            name,
81            port
82        );
83
84        #[cfg(feature = "api")]
85        {
86            self.statistics.register(*addr);
87
88            crate::api::events::send_with_stream("allocated", || {
89                json!({
90                    "session": {
91                        "address": addr.address,
92                        "interface": addr.interface,
93                    },
94                    "username": name,
95                    "port": port,
96                })
97            });
98        }
99    }
100
101    /// channel binding request
102    ///
103    /// The server MAY impose restrictions on the IP address and port values
104    /// allowed in the XOR-PEER-ADDRESS attribute; if a value is not allowed,
105    /// the server rejects the request with a 403 (Forbidden) error.
106    ///
107    /// If the request is valid, but the server is unable to fulfill the
108    /// request due to some capacity limit or similar, the server replies
109    /// with a 508 (Insufficient Capacity) error.
110    ///
111    /// Otherwise, the server replies with a ChannelBind success response.
112    /// There are no required attributes in a successful ChannelBind
113    /// response.
114    ///
115    /// If the server can satisfy the request, then the server creates or
116    /// refreshes the channel binding using the channel number in the
117    /// CHANNEL-NUMBER attribute and the interface address in the XOR-PEER-
118    /// ADDRESS attribute.  The server also installs or refreshes a
119    /// permission for the IP address in the XOR-PEER-ADDRESS attribute as
120    /// described in Section 9.
121    ///
122    /// NOTE: A server need not do anything special to implement
123    /// idempotency of ChannelBind requests over UDP using the
124    /// "stateless stack approach".  Retransmitted ChannelBind requests
125    /// will simply refresh the channel binding and the corresponding
126    /// permission.  Furthermore, the client must wait 5 minutes before
127    /// binding a previously bound channel number or peer address to a
128    /// different channel, eliminating the possibility that the
129    /// transaction would initially fail but succeed on a
130    /// retransmission.
131    #[allow(clippy::let_underscore_future)]
132    fn channel_bind(&self, addr: &SessionAddr, name: &str, channel: u16) {
133        log::info!(
134            "channel bind: address={:?}, interface={:?}, username={:?}, channel={}",
135            addr.address,
136            addr.interface,
137            name,
138            channel
139        );
140
141        #[cfg(feature = "api")]
142        {
143            crate::api::events::send_with_stream("channel_bind", || {
144                json!({
145                    "session": {
146                        "address": addr.address,
147                        "interface": addr.interface,
148                    },
149                    "username": name,
150                    "channel": channel,
151                })
152            });
153        }
154    }
155
156    /// create permission request
157    ///
158    /// [rfc8489](https://tools.ietf.org/html/rfc8489)
159    ///
160    /// When the server receives the CreatePermission request, it processes
161    /// as per [Section 5](https://tools.ietf.org/html/rfc8656#section-5)
162    /// plus the specific rules mentioned here.
163    ///
164    /// The message is checked for validity.  The CreatePermission request
165    /// MUST contain at least one XOR-PEER-ADDRESS attribute and MAY contain
166    /// multiple such attributes.  If no such attribute exists, or if any of
167    /// these attributes are invalid, then a 400 (Bad Request) error is
168    /// returned.  If the request is valid, but the server is unable to
169    /// satisfy the request due to some capacity limit or similar, then a 508
170    /// (Insufficient Capacity) error is returned.
171    ///
172    /// If an XOR-PEER-ADDRESS attribute contains an address of an address
173    /// family that is not the same as that of a relayed interface address
174    /// for the allocation, the server MUST generate an error response with
175    /// the 443 (Peer Address Family Mismatch) response code.
176    ///
177    /// The server MAY impose restrictions on the IP address allowed in the
178    /// XOR-PEER-ADDRESS attribute; if a value is not allowed, the server
179    /// rejects the request with a 403 (Forbidden) error.
180    ///
181    /// If the message is valid and the server is capable of carrying out the
182    /// request, then the server installs or refreshes a permission for the
183    /// IP address contained in each XOR-PEER-ADDRESS attribute as described
184    /// in [Section 9](https://tools.ietf.org/html/rfc8656#section-9).  
185    /// The port portion of each attribute is ignored and may be any arbitrary
186    /// value.
187    ///
188    /// The server then responds with a CreatePermission success response.
189    /// There are no mandatory attributes in the success response.
190    ///
191    /// > NOTE: A server need not do anything special to implement
192    /// idempotency of CreatePermission requests over UDP using the
193    /// "stateless stack approach".  Retransmitted CreatePermission
194    /// requests will simply refresh the permissions.
195    #[allow(clippy::let_underscore_future)]
196    fn create_permission(&self, addr: &SessionAddr, name: &str, ports: &[u16]) {
197        log::info!(
198            "create permission: address={:?}, interface={:?}, username={:?}, ports={:?}",
199            addr.address,
200            addr.interface,
201            name,
202            ports
203        );
204
205        #[cfg(feature = "api")]
206        {
207            crate::api::events::send_with_stream("create_permission", || {
208                json!({
209                    "session": {
210                        "address": addr.address,
211                        "interface": addr.interface,
212                    },
213                    "username": name,
214                    "ports": ports,
215                })
216            });
217        }
218    }
219
220    /// refresh request
221    ///
222    /// If the server receives a Refresh Request with a REQUESTED-ADDRESS-
223    /// FAMILY attribute and the attribute value does not match the address
224    /// family of the allocation, the server MUST reply with a 443 (Peer
225    /// Address Family Mismatch) Refresh error response.
226    ///
227    /// The server computes a value called the "desired lifetime" as follows:
228    /// if the request contains a LIFETIME attribute and the attribute value
229    /// is zero, then the "desired lifetime" is zero.  Otherwise, if the
230    /// request contains a LIFETIME attribute, then the server computes the
231    /// minimum of the client's requested lifetime and the server's maximum
232    /// allowed lifetime.  If this computed value is greater than the default
233    /// lifetime, then the "desired lifetime" is the computed value.
234    /// Otherwise, the "desired lifetime" is the default lifetime.
235    ///
236    /// Subsequent processing depends on the "desired lifetime" value:
237    ///
238    /// * If the "desired lifetime" is zero, then the request succeeds and
239    /// the allocation is deleted.
240    ///
241    /// * If the "desired lifetime" is non-zero, then the request succeeds
242    /// and the allocation's time-to-expiry is set to the "desired
243    /// lifetime".
244    ///
245    /// If the request succeeds, then the server sends a success response
246    /// containing:
247    ///
248    /// * A LIFETIME attribute containing the current value of the time-to-
249    /// expiry timer.
250    ///
251    /// NOTE: A server need not do anything special to implement
252    /// idempotency of Refresh requests over UDP using the "stateless
253    /// stack approach".  Retransmitted Refresh requests with a non-
254    /// zero "desired lifetime" will simply refresh the allocation.  A
255    /// retransmitted Refresh request with a zero "desired lifetime"
256    /// will cause a 437 (Allocation Mismatch) response if the
257    /// allocation has already been deleted, but the client will treat
258    /// this as equivalent to a success response (see below).
259    #[allow(clippy::let_underscore_future)]
260    fn refresh(&self, addr: &SessionAddr, name: &str, lifetime: u32) {
261        log::info!(
262            "refresh: address={:?}, interface={:?}, username={:?}, lifetime={}",
263            addr.address,
264            addr.interface,
265            name,
266            lifetime
267        );
268
269        #[cfg(feature = "api")]
270        {
271            crate::api::events::send_with_stream("refresh", || {
272                json!({
273                    "session": {
274                        "address": addr.address,
275                        "interface": addr.interface,
276                    },
277                    "username": name,
278                    "lifetime": lifetime,
279                })
280            });
281        }
282    }
283
284    /// session closed
285    ///
286    /// Triggered when the session leaves from the turn. Possible reasons: the
287    /// session life cycle has expired, external active deletion, or active
288    /// exit of the session.
289    #[allow(clippy::let_underscore_future)]
290    fn closed(&self, addr: &SessionAddr, name: &str) {
291        log::info!(
292            "closed: address={:?}, interface={:?}, username={:?}",
293            addr.address,
294            addr.interface,
295            name
296        );
297
298        #[cfg(feature = "api")]
299        {
300            self.statistics.unregister(&addr);
301
302            crate::api::events::send_with_stream("closed", || {
303                json!({
304                    "session": {
305                        "address": addr.address,
306                        "interface": addr.interface,
307                    },
308                    "username": name,
309                })
310            });
311        }
312    }
313}