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}