Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/dialog/invitation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ pub struct InviteOption {
pub destination: Option<SipAddr>,
pub content_type: Option<String>,
pub offer: Option<Vec<u8>>,
/// Local Contact. On stream transports its host and port are normally
/// replaced with the outgoing connection's local address (and the scheme
/// becomes `sips` over TLS). A Contact that already carries a `transport`
/// parameter is treated as deliberately built by the caller, for example
/// from the address a registrar reported behind NAT, and is sent unchanged.
pub contact: crate::sip::Uri,
pub credential: Option<Credential>,
pub headers: Option<Vec<crate::sip::Header>>,
Expand Down Expand Up @@ -421,7 +426,17 @@ impl DialogLayer {
request.headers.push(route.clone().into());
}

let contact = if let Some(ref addr) = transport_addr {
// A Contact that names its transport was built deliberately by the
// caller (e.g. with the public address learned from a REGISTER
// response behind NAT); keep it rather than rewriting it to the local
// socket address.
let caller_built_contact = opt
.contact
.params
.iter()
.any(|p| matches!(p, crate::sip::Param::Transport(_)));
let contact = if let Some(addr) = transport_addr.as_ref().filter(|_| !caller_built_contact)
{
let mut uri = opt.contact.clone();
uri.host_with_port = addr.addr.clone();
if !uri
Expand Down
48 changes: 48 additions & 0 deletions src/dialog/tests/test_dialog_layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,54 @@ async fn test_make_invite_request_with_tls_transport_uses_sips_scheme() -> crate
Ok(())
}

#[cfg(feature = "rustls")]
#[tokio::test]
async fn test_make_invite_request_keeps_caller_built_contact() -> crate::Result<()> {
let token = CancellationToken::new();
let tl = TransportLayer::new(token.child_token());

let tls_addr = "192.168.1.10:5061".parse()?;
let tls_listener = TlsListenerConnection::new(tls_addr, None, TlsConfig::default()).await?;
tl.add_transport(crate::transport::SipConnection::TlsListener(tls_listener));

let endpoint = EndpointBuilder::new()
.with_user_agent("rsipstack-test")
.with_transport_layer(tl)
.build();
let dialog_layer = DialogLayer::new(endpoint.inner.clone());

let destination = SipAddr {
r#type: Some(Transport::Tls),
addr: HostWithPort {
host: crate::sip::Host::IpAddr(std::net::IpAddr::V4(std::net::Ipv4Addr::new(
10, 0, 0, 1,
))),
port: Some(5061.into()),
},
};

// A Contact built from the address a registrar reported behind NAT.
let public_contact = crate::sip::Uri::try_from("sip:alice@203.0.113.7:40000;transport=tls")?;
let opt = crate::dialog::invitation::InviteOption {
caller: crate::sip::Uri::try_from("sip:alice@example.com")?,
callee: crate::sip::Uri::try_from("sip:bob@example.com")?,
contact: public_contact.clone(),
destination: Some(destination),
..Default::default()
};

let request = dialog_layer.make_invite_request(&opt)?;
let contact = request.contact_header()?.typed()?;

assert_eq!(
contact.uri, public_contact,
"a Contact with an explicit transport should be sent unchanged"
);
assert_eq!(contact.uri.scheme, Some(crate::sip::Scheme::Sip));

Ok(())
}

#[tokio::test]
async fn test_make_invite_request_preloads_service_route() -> crate::Result<()> {
let token = CancellationToken::new();
Expand Down