use rsipstack::transport::SipAddr;
use arc_swap::ArcSwap;
use rsipstack::rsip::{headers::ToTypedHeader, prelude::HeadersExt};
use rsipstack::{
transaction::endpoint::MessageInspector,
};
use std::{net::IpAddr, sync::Arc};
pub type SharedPublicAddress = Arc<ArcSwap<rsipstack::rsip::HostWithPort>>;
pub fn normalize_transport(transport: Option<&rsipstack::rsip::Transport>) -> rsipstack::rsip::Transport {
transport.cloned().unwrap_or(rsipstack::rsip::Transport::Udp)
}
pub fn transport_for_uri(uri: &rsipstack::rsip::Uri) -> rsipstack::rsip::Transport {
if matches!(uri.scheme, Some(rsipstack::rsip::Scheme::Sips)) {
return rsipstack::rsip::Transport::Tls;
}
uri.params
.iter()
.find_map(|param| match param {
rsipstack::rsip::Param::Transport(transport) => Some(transport.clone()),
_ => None,
})
.unwrap_or(rsipstack::rsip::Transport::Udp)
}
pub fn find_local_addr_for_uri(addrs: &[SipAddr], uri: &rsipstack::rsip::Uri) -> Option<SipAddr> {
let transport = transport_for_uri(uri);
addrs.iter()
.find(|addr| normalize_transport(addr.r#type.as_ref()) == transport)
.cloned()
}
pub fn contact_needs_public_resolution(contact: &rsipstack::rsip::Uri) -> bool {
if contact.scheme.is_none() {
return true;
}
match &contact.host_with_port.host {
rsipstack::rsip::Host::Domain(domain) => {
let host = domain.to_string();
host.eq_ignore_ascii_case("localhost")
}
rsipstack::rsip::Host::IpAddr(ip) => is_local_or_unspecified(ip),
}
}
pub fn build_contact_uri(
local_addr: &SipAddr,
learned_addr: Option<rsipstack::rsip::HostWithPort>,
username: Option<&str>,
template: Option<&rsipstack::rsip::Uri>,
) -> rsipstack::rsip::Uri {
let mut uri = template
.cloned()
.unwrap_or_else(|| rsipstack::rsip::Uri::from(local_addr));
uri.host_with_port = learned_addr.unwrap_or_else(|| local_addr.addr.clone());
if uri.scheme.is_none() {
uri.scheme = Some(match local_addr.r#type {
Some(rsipstack::rsip::Transport::Tls)
| Some(rsipstack::rsip::Transport::Wss)
| Some(rsipstack::rsip::Transport::TlsSctp) => rsipstack::rsip::Scheme::Sips,
_ => rsipstack::rsip::Scheme::Sip,
});
}
if uri.auth.is_none() {
if let Some(username) = username.filter(|value| !value.is_empty()) {
uri.auth = Some(rsipstack::rsip::Auth {
user: username.to_string(),
password: None,
});
}
}
uri
}
pub fn build_contact(
local_addr: &SipAddr,
contact_address: Option<rsipstack::rsip::HostWithPort>,
username: Option<&str>,
template: Option<&rsipstack::rsip::Uri>,
) -> rsipstack::rsip::typed::Contact {
let contact_uri = build_contact_uri(local_addr, contact_address, username, template);
rsipstack::rsip::typed::Contact {
display_name: None,
uri: contact_uri,
params: vec![],
}
}
pub fn build_public_contact_uri(
learned_public_address: &SharedPublicAddress,
auto_learn_public_address: bool,
local_addr: &SipAddr,
username: Option<&str>,
template: Option<&rsipstack::rsip::Uri>,
) -> rsipstack::rsip::Uri {
let selected_addr = if auto_learn_public_address
&& normalize_transport(local_addr.r#type.as_ref()) == rsipstack::rsip::Transport::Udp
{
Some(learned_public_address.load_full().as_ref().clone())
} else {
Some(local_addr.addr.clone())
};
build_contact_uri(local_addr, selected_addr, username, template)
}
pub struct LearningMessageInspector {
learned_public_address: SharedPublicAddress,
next: Option<Box<dyn MessageInspector>>,
}
impl LearningMessageInspector {
pub fn new(
initial_address: rsipstack::rsip::HostWithPort,
next: Option<Box<dyn MessageInspector>>,
) -> Self {
Self {
learned_public_address: Arc::new(ArcSwap::from_pointee(initial_address)),
next,
}
}
pub fn shared_public_address(&self) -> SharedPublicAddress {
self.learned_public_address.clone()
}
}
impl MessageInspector for LearningMessageInspector {
fn before_send(&self, msg: rsipstack::rsip::SipMessage, dest: Option<&SipAddr>) -> rsipstack::rsip::SipMessage {
if let Some(next) = &self.next {
next.before_send(msg, dest)
} else {
msg
}
}
fn after_received(&self, msg: rsipstack::rsip::SipMessage, from: Option<&SipAddr>) -> rsipstack::rsip::SipMessage {
if let rsipstack::rsip::SipMessage::Response(response) = &msg
&& let Ok(via) = response.via_header()
&& let Ok(via) = via.typed()
&& via.transport == rsipstack::rsip::Transport::Udp
&& let Some(host_with_port) = response.via_received()
{
self.learned_public_address
.rcu(|previous: &Arc<rsipstack::rsip::HostWithPort>| {
if should_update_address(previous.as_ref(), &host_with_port) {
Arc::new(host_with_port.clone())
} else {
previous.clone()
}
});
}
if let Some(next) = &self.next {
next.after_received(msg, from)
} else {
msg
}
}
}
pub fn should_update_address(
previous: &rsipstack::rsip::HostWithPort,
current: &rsipstack::rsip::HostWithPort,
) -> bool {
if previous == current {
return false;
}
let previous_is_public = is_public_address(previous);
let current_is_public = is_public_address(current);
(!previous_is_public && current_is_public)
|| (previous_is_public && current_is_public && previous != current)
}
fn is_public_address(host_with_port: &rsipstack::rsip::HostWithPort) -> bool {
match &host_with_port.host {
rsipstack::rsip::Host::Domain(domain) => !domain.to_string().eq_ignore_ascii_case("localhost"),
rsipstack::rsip::Host::IpAddr(ip) => !is_local_or_unspecified(ip),
}
}
fn is_local_or_unspecified(ip: &IpAddr) -> bool {
ip.is_loopback() || ip.is_unspecified()
}
#[cfg(test)]
mod tests {
use super::{
SharedPublicAddress, build_contact, build_contact_uri, build_public_contact_uri,
contact_needs_public_resolution, find_local_addr_for_uri, should_update_address,
transport_for_uri,
};
use arc_swap::ArcSwap;
use rsipstack::rsip::transport::Transport;
use rsipstack::transaction::endpoint::MessageInspector;
use rsipstack::transport::SipAddr;
use std::sync::Arc;
#[test]
fn learns_public_address_from_response_via() {
let response: rsipstack::rsip::Response = concat!(
"SIP/2.0 401 Unauthorized\r\n",
"Via: SIP/2.0/UDP 10.0.0.1:5060;branch=z9hG4bK-1;received=203.0.113.10;rport=62000\r\n",
"Content-Length: 0\r\n",
"\r\n"
)
.try_into()
.unwrap();
let inspector = super::LearningMessageInspector::new(
"127.0.0.1:5060"
.parse::<std::net::SocketAddr>()
.unwrap()
.into(),
None,
);
let cache = inspector.shared_public_address();
inspector.after_received(
rsipstack::rsip::SipMessage::Response(response),
Some(&SipAddr {
r#type: Some(Transport::Udp),
addr: "10.0.0.1:5060"
.parse::<std::net::SocketAddr>()
.unwrap()
.into(),
}),
);
assert_eq!(cache.load_full().as_ref().to_string(), "203.0.113.10:62000");
}
#[test]
fn builds_contact_using_learned_public_address() {
let local_addr = SipAddr {
r#type: Some(Transport::Udp),
addr: "10.0.0.5:5060"
.parse::<std::net::SocketAddr>()
.unwrap()
.into(),
};
let template: rsipstack::rsip::Uri = "sip:alice@127.0.0.1:5060".try_into().unwrap();
let learned_addr = Some(
"203.0.113.10:62000"
.parse::<std::net::SocketAddr>()
.unwrap()
.into(),
);
let contact = build_contact_uri(&local_addr, learned_addr, Some("alice"), Some(&template));
assert_eq!(contact.to_string(), "sip:alice@203.0.113.10:62000");
}
#[test]
fn identifies_contacts_that_need_resolution() {
let local_contact: rsipstack::rsip::Uri = "sip:alice@127.0.0.1:5060".try_into().unwrap();
let remote_contact: rsipstack::rsip::Uri = "sip:alice@203.0.113.10:62000".try_into().unwrap();
assert!(contact_needs_public_resolution(&local_contact));
assert!(!contact_needs_public_resolution(&remote_contact));
}
#[test]
fn selects_local_addr_for_uri_transport() {
let addrs = vec![
SipAddr {
r#type: Some(Transport::Udp),
addr: "10.0.0.5:5060"
.parse::<std::net::SocketAddr>()
.unwrap()
.into(),
},
SipAddr {
r#type: Some(Transport::Tls),
addr: "10.0.0.5:5061"
.parse::<std::net::SocketAddr>()
.unwrap()
.into(),
},
];
let uri: rsipstack::rsip::Uri = "sips:alice@example.com".try_into().unwrap();
let selected = find_local_addr_for_uri(&addrs, &uri).unwrap();
assert_eq!(selected.to_string(), "TLS 10.0.0.5:5061");
}
#[test]
fn builds_public_contact_from_shared_cache() {
let cache: SharedPublicAddress = Arc::new(ArcSwap::from_pointee(
"203.0.113.20:62000"
.parse::<std::net::SocketAddr>()
.unwrap()
.into(),
));
let local_addr = SipAddr {
r#type: Some(Transport::Udp),
addr: "10.0.0.5:5060"
.parse::<std::net::SocketAddr>()
.unwrap()
.into(),
};
let contact = build_public_contact_uri(&cache, true, &local_addr, Some("alice"), None);
assert_eq!(contact.to_string(), "sip:alice@203.0.113.20:62000");
}
#[test]
fn builds_typed_contact() {
let local_addr = SipAddr {
r#type: Some(Transport::Udp),
addr: "10.0.0.5:5060"
.parse::<std::net::SocketAddr>()
.unwrap()
.into(),
};
let contact = build_contact(
&local_addr,
Some(
"203.0.113.20:62000"
.parse::<std::net::SocketAddr>()
.unwrap()
.into(),
),
Some("alice"),
None,
);
assert_eq!(contact.to_string(), "<sip:alice@203.0.113.20:62000>");
}
#[test]
fn keeps_configured_contact_for_tls() {
let cache: SharedPublicAddress = Arc::new(ArcSwap::from_pointee(
"203.0.113.20:62000"
.parse::<std::net::SocketAddr>()
.unwrap()
.into(),
));
let local_addr = SipAddr {
r#type: Some(Transport::Tls),
addr: "10.0.0.5:5061"
.parse::<std::net::SocketAddr>()
.unwrap()
.into(),
};
let contact = build_public_contact_uri(&cache, true, &local_addr, Some("alice"), None);
assert_eq!(contact.to_string(), "sips:alice@10.0.0.5:5061;transport=TLS");
}
#[test]
fn infers_transport_from_uri() {
let sips_uri: rsipstack::rsip::Uri = "sips:alice@example.com".try_into().unwrap();
let tcp_uri: rsipstack::rsip::Uri = "sip:alice@example.com;transport=tcp".try_into().unwrap();
assert_eq!(transport_for_uri(&sips_uri), Transport::Tls);
assert_eq!(transport_for_uri(&tcp_uri), Transport::Tcp);
}
#[test]
fn updates_learned_address_from_local_to_public() {
let previous: rsipstack::rsip::HostWithPort = "127.0.0.1:5060"
.parse::<std::net::SocketAddr>()
.unwrap()
.into();
let current: rsipstack::rsip::HostWithPort = "203.0.113.10:62000"
.parse::<std::net::SocketAddr>()
.unwrap()
.into();
assert!(should_update_address(&previous, ¤t,));
}
#[test]
fn does_not_update_learned_address_when_unchanged() {
let current: rsipstack::rsip::HostWithPort = "203.0.113.10:62000"
.parse::<std::net::SocketAddr>()
.unwrap()
.into();
assert!(!should_update_address(¤t, ¤t,));
}
}