Skip to main content

active_call/useragent/
public_address.rs

1use rsipstack::transport::SipAddr;
2use arc_swap::ArcSwap;
3use rsipstack::rsip::{headers::ToTypedHeader, prelude::HeadersExt};
4use rsipstack::{
5    transaction::endpoint::MessageInspector,
6};
7use std::{net::IpAddr, sync::Arc};
8
9pub type SharedPublicAddress = Arc<ArcSwap<rsipstack::rsip::HostWithPort>>;
10
11pub fn normalize_transport(transport: Option<&rsipstack::rsip::Transport>) -> rsipstack::rsip::Transport {
12    transport.cloned().unwrap_or(rsipstack::rsip::Transport::Udp)
13}
14
15pub fn transport_for_uri(uri: &rsipstack::rsip::Uri) -> rsipstack::rsip::Transport {
16    if matches!(uri.scheme, Some(rsipstack::rsip::Scheme::Sips)) {
17        return rsipstack::rsip::Transport::Tls;
18    }
19
20    uri.params
21        .iter()
22        .find_map(|param| match param {
23            rsipstack::rsip::Param::Transport(transport) => Some(transport.clone()),
24            _ => None,
25        })
26        .unwrap_or(rsipstack::rsip::Transport::Udp)
27}
28
29pub fn find_local_addr_for_uri(addrs: &[SipAddr], uri: &rsipstack::rsip::Uri) -> Option<SipAddr> {
30    let transport = transport_for_uri(uri);
31    addrs.iter()
32        .find(|addr| normalize_transport(addr.r#type.as_ref()) == transport)
33        .cloned()
34}
35
36pub fn contact_needs_public_resolution(contact: &rsipstack::rsip::Uri) -> bool {
37    if contact.scheme.is_none() {
38        return true;
39    }
40
41    match &contact.host_with_port.host {
42        rsipstack::rsip::Host::Domain(domain) => {
43            let host = domain.to_string();
44            host.eq_ignore_ascii_case("localhost")
45        }
46        rsipstack::rsip::Host::IpAddr(ip) => is_local_or_unspecified(ip),
47    }
48}
49
50pub fn build_contact_uri(
51    local_addr: &SipAddr,
52    learned_addr: Option<rsipstack::rsip::HostWithPort>,
53    username: Option<&str>,
54    template: Option<&rsipstack::rsip::Uri>,
55) -> rsipstack::rsip::Uri {
56    let mut uri = template
57        .cloned()
58        .unwrap_or_else(|| rsipstack::rsip::Uri::from(local_addr));
59
60    uri.host_with_port = learned_addr.unwrap_or_else(|| local_addr.addr.clone());
61    if uri.scheme.is_none() {
62        uri.scheme = Some(match local_addr.r#type {
63            Some(rsipstack::rsip::Transport::Tls)
64            | Some(rsipstack::rsip::Transport::Wss)
65            | Some(rsipstack::rsip::Transport::TlsSctp) => rsipstack::rsip::Scheme::Sips,
66            _ => rsipstack::rsip::Scheme::Sip,
67        });
68    }
69
70    if uri.auth.is_none() {
71        if let Some(username) = username.filter(|value| !value.is_empty()) {
72            uri.auth = Some(rsipstack::rsip::Auth {
73                user: username.to_string(),
74                password: None,
75            });
76        }
77    }
78
79    uri
80}
81
82pub fn build_contact(
83    local_addr: &SipAddr,
84    contact_address: Option<rsipstack::rsip::HostWithPort>,
85    username: Option<&str>,
86    template: Option<&rsipstack::rsip::Uri>,
87) -> rsipstack::rsip::typed::Contact {
88    let contact_uri = build_contact_uri(local_addr, contact_address, username, template);
89    rsipstack::rsip::typed::Contact {
90        display_name: None,
91        uri: contact_uri,
92        params: vec![],
93    }
94}
95
96pub fn build_public_contact_uri(
97    learned_public_address: &SharedPublicAddress,
98    auto_learn_public_address: bool,
99    local_addr: &SipAddr,
100    username: Option<&str>,
101    template: Option<&rsipstack::rsip::Uri>,
102) -> rsipstack::rsip::Uri {
103    let selected_addr = if auto_learn_public_address
104        && normalize_transport(local_addr.r#type.as_ref()) == rsipstack::rsip::Transport::Udp
105    {
106        Some(learned_public_address.load_full().as_ref().clone())
107    } else {
108        Some(local_addr.addr.clone())
109    };
110    build_contact_uri(local_addr, selected_addr, username, template)
111}
112
113pub struct LearningMessageInspector {
114    learned_public_address: SharedPublicAddress,
115    next: Option<Box<dyn MessageInspector>>,
116}
117
118impl LearningMessageInspector {
119    pub fn new(
120        initial_address: rsipstack::rsip::HostWithPort,
121        next: Option<Box<dyn MessageInspector>>,
122    ) -> Self {
123        Self {
124            learned_public_address: Arc::new(ArcSwap::from_pointee(initial_address)),
125            next,
126        }
127    }
128
129    pub fn shared_public_address(&self) -> SharedPublicAddress {
130        self.learned_public_address.clone()
131    }
132}
133
134impl MessageInspector for LearningMessageInspector {
135    fn before_send(&self, msg: rsipstack::rsip::SipMessage, dest: Option<&SipAddr>) -> rsipstack::rsip::SipMessage {
136        if let Some(next) = &self.next {
137            next.before_send(msg, dest)
138        } else {
139            msg
140        }
141    }
142
143    fn after_received(&self, msg: rsipstack::rsip::SipMessage, from: &SipAddr) -> rsipstack::rsip::SipMessage {
144        if let rsipstack::rsip::SipMessage::Response(response) = &msg
145            && let Ok(via) = response.via_header()
146            && let Ok(via) = via.typed()
147            && via.transport == rsipstack::rsip::Transport::Udp
148            && let Some(host_with_port) = response.via_received()
149        {
150            self.learned_public_address
151                .rcu(|previous: &Arc<rsipstack::rsip::HostWithPort>| {
152                    if should_update_address(previous.as_ref(), &host_with_port) {
153                        Arc::new(host_with_port.clone())
154                    } else {
155                        previous.clone()
156                    }
157                });
158        }
159
160        if let Some(next) = &self.next {
161            next.after_received(msg, from)
162        } else {
163            msg
164        }
165    }
166}
167
168pub fn should_update_address(
169    previous: &rsipstack::rsip::HostWithPort,
170    current: &rsipstack::rsip::HostWithPort,
171) -> bool {
172    if previous == current {
173        return false;
174    }
175
176    let previous_is_public = is_public_address(previous);
177    let current_is_public = is_public_address(current);
178    (!previous_is_public && current_is_public)
179        || (previous_is_public && current_is_public && previous != current)
180}
181
182fn is_public_address(host_with_port: &rsipstack::rsip::HostWithPort) -> bool {
183    match &host_with_port.host {
184        rsipstack::rsip::Host::Domain(domain) => !domain.to_string().eq_ignore_ascii_case("localhost"),
185        rsipstack::rsip::Host::IpAddr(ip) => !is_local_or_unspecified(ip),
186    }
187}
188
189fn is_local_or_unspecified(ip: &IpAddr) -> bool {
190    ip.is_loopback() || ip.is_unspecified()
191}
192
193#[cfg(test)]
194mod tests {
195    use super::{
196        SharedPublicAddress, build_contact, build_contact_uri, build_public_contact_uri,
197        contact_needs_public_resolution, find_local_addr_for_uri, should_update_address,
198        transport_for_uri,
199    };
200    use arc_swap::ArcSwap;
201    use rsipstack::rsip::transport::Transport;
202    use rsipstack::transaction::endpoint::MessageInspector;
203    use rsipstack::transport::SipAddr;
204    use std::sync::Arc;
205
206    #[test]
207    fn learns_public_address_from_response_via() {
208        let response: rsipstack::rsip::Response = concat!(
209            "SIP/2.0 401 Unauthorized\r\n",
210            "Via: SIP/2.0/UDP 10.0.0.1:5060;branch=z9hG4bK-1;received=203.0.113.10;rport=62000\r\n",
211            "Content-Length: 0\r\n",
212            "\r\n"
213        )
214        .try_into()
215        .unwrap();
216
217        let inspector = super::LearningMessageInspector::new(
218            "127.0.0.1:5060"
219                .parse::<std::net::SocketAddr>()
220                .unwrap()
221                .into(),
222            None,
223        );
224        let cache = inspector.shared_public_address();
225        inspector.after_received(
226            rsipstack::rsip::SipMessage::Response(response),
227            &SipAddr {
228                r#type: Some(Transport::Udp),
229                addr: "10.0.0.1:5060"
230                    .parse::<std::net::SocketAddr>()
231                    .unwrap()
232                    .into(),
233            },
234        );
235        assert_eq!(cache.load_full().as_ref().to_string(), "203.0.113.10:62000");
236    }
237
238    #[test]
239    fn builds_contact_using_learned_public_address() {
240        let local_addr = SipAddr {
241            r#type: Some(Transport::Udp),
242            addr: "10.0.0.5:5060"
243                .parse::<std::net::SocketAddr>()
244                .unwrap()
245                .into(),
246        };
247        let template: rsipstack::rsip::Uri = "sip:alice@127.0.0.1:5060".try_into().unwrap();
248        let learned_addr = Some(
249            "203.0.113.10:62000"
250                .parse::<std::net::SocketAddr>()
251                .unwrap()
252                .into(),
253        );
254
255        let contact = build_contact_uri(&local_addr, learned_addr, Some("alice"), Some(&template));
256        assert_eq!(contact.to_string(), "sip:alice@203.0.113.10:62000");
257    }
258
259    #[test]
260    fn identifies_contacts_that_need_resolution() {
261        let local_contact: rsipstack::rsip::Uri = "sip:alice@127.0.0.1:5060".try_into().unwrap();
262        let remote_contact: rsipstack::rsip::Uri = "sip:alice@203.0.113.10:62000".try_into().unwrap();
263        assert!(contact_needs_public_resolution(&local_contact));
264        assert!(!contact_needs_public_resolution(&remote_contact));
265    }
266
267    #[test]
268    fn selects_local_addr_for_uri_transport() {
269        let addrs = vec![
270            SipAddr {
271                r#type: Some(Transport::Udp),
272                addr: "10.0.0.5:5060"
273                    .parse::<std::net::SocketAddr>()
274                    .unwrap()
275                    .into(),
276            },
277            SipAddr {
278                r#type: Some(Transport::Tls),
279                addr: "10.0.0.5:5061"
280                    .parse::<std::net::SocketAddr>()
281                    .unwrap()
282                    .into(),
283            },
284        ];
285
286        let uri: rsipstack::rsip::Uri = "sips:alice@example.com".try_into().unwrap();
287        let selected = find_local_addr_for_uri(&addrs, &uri).unwrap();
288
289        assert_eq!(selected.to_string(), "TLS 10.0.0.5:5061");
290    }
291
292    #[test]
293    fn builds_public_contact_from_shared_cache() {
294        let cache: SharedPublicAddress = Arc::new(ArcSwap::from_pointee(
295            "203.0.113.20:62000"
296                .parse::<std::net::SocketAddr>()
297                .unwrap()
298                .into(),
299        ));
300        let local_addr = SipAddr {
301            r#type: Some(Transport::Udp),
302            addr: "10.0.0.5:5060"
303                .parse::<std::net::SocketAddr>()
304                .unwrap()
305                .into(),
306        };
307
308        let contact = build_public_contact_uri(&cache, true, &local_addr, Some("alice"), None);
309        assert_eq!(contact.to_string(), "sip:alice@203.0.113.20:62000");
310    }
311
312    #[test]
313    fn builds_typed_contact() {
314        let local_addr = SipAddr {
315            r#type: Some(Transport::Udp),
316            addr: "10.0.0.5:5060"
317                .parse::<std::net::SocketAddr>()
318                .unwrap()
319                .into(),
320        };
321        let contact = build_contact(
322            &local_addr,
323            Some(
324                "203.0.113.20:62000"
325                    .parse::<std::net::SocketAddr>()
326                    .unwrap()
327                    .into(),
328            ),
329            Some("alice"),
330            None,
331        );
332        assert_eq!(contact.to_string(), "<sip:alice@203.0.113.20:62000>");
333    }
334
335    #[test]
336    fn keeps_configured_contact_for_tls() {
337        let cache: SharedPublicAddress = Arc::new(ArcSwap::from_pointee(
338            "203.0.113.20:62000"
339                .parse::<std::net::SocketAddr>()
340                .unwrap()
341                .into(),
342        ));
343        let local_addr = SipAddr {
344            r#type: Some(Transport::Tls),
345            addr: "10.0.0.5:5061"
346                .parse::<std::net::SocketAddr>()
347                .unwrap()
348                .into(),
349        };
350
351        let contact = build_public_contact_uri(&cache, true, &local_addr, Some("alice"), None);
352        assert_eq!(contact.to_string(), "sips:alice@10.0.0.5:5061;transport=TLS");
353    }
354
355    #[test]
356    fn infers_transport_from_uri() {
357        let sips_uri: rsipstack::rsip::Uri = "sips:alice@example.com".try_into().unwrap();
358        let tcp_uri: rsipstack::rsip::Uri = "sip:alice@example.com;transport=tcp".try_into().unwrap();
359        assert_eq!(transport_for_uri(&sips_uri), Transport::Tls);
360        assert_eq!(transport_for_uri(&tcp_uri), Transport::Tcp);
361    }
362
363    #[test]
364    fn updates_learned_address_from_local_to_public() {
365        let previous: rsipstack::rsip::HostWithPort = "127.0.0.1:5060"
366            .parse::<std::net::SocketAddr>()
367            .unwrap()
368            .into();
369        let current: rsipstack::rsip::HostWithPort = "203.0.113.10:62000"
370            .parse::<std::net::SocketAddr>()
371            .unwrap()
372            .into();
373
374        assert!(should_update_address(&previous, &current,));
375    }
376
377    #[test]
378    fn does_not_update_learned_address_when_unchanged() {
379        let current: rsipstack::rsip::HostWithPort = "203.0.113.10:62000"
380            .parse::<std::net::SocketAddr>()
381            .unwrap()
382            .into();
383
384        assert!(!should_update_address(&current, &current,));
385    }
386}