From 1feecc53d5141449999cec3c7007fa49f74a4879 Mon Sep 17 00:00:00 2001 From: Venky Date: Sun, 29 Mar 2026 16:20:01 +0400 Subject: [PATCH] fix: rebuild Contact from public_address after 401 NAT challenge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After a 401 Unauthorized response, the Via rport/received parameters reveal the actual NAT-mapped source port, which may differ from the STUN-discovered port. The auth retry reused the original Contact with the stale port, causing registration to succeed but incoming requests to be routed to the wrong address. Now rebuilds the Contact header from the updated public_address after handle_client_authenticate, ensuring the retry uses the correct NAT mapping observed by the server. Also removes a duplicate rport push in handle_client_authenticate — get_via already adds rport, and duplicate Via params can cause some servers to silently drop the request. --- src/dialog/authenticate.rs | 4 +++- src/dialog/registration.rs | 21 +++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/dialog/authenticate.rs b/src/dialog/authenticate.rs index d991a4ea..7a4e55d0 100644 --- a/src/dialog/authenticate.rs +++ b/src/dialog/authenticate.rs @@ -253,7 +253,9 @@ pub async fn handle_client_authenticate( let params = &mut via_header.params; params.retain(|p| !matches!(p, rsip::Param::Branch(_))); params.push(make_via_branch()); - params.push(Param::Other("rport".into(), None)); + if !params.iter().any(|p| matches!(p, Param::Other(key, _) if key.value().eq_ignore_ascii_case("rport"))) { + params.push(Param::Other("rport".into(), None)); + } new_req.headers_mut().unique_push(via_header.into()); new_req.headers_mut().retain(|h| { diff --git a/src/dialog/registration.rs b/src/dialog/registration.rs index 57f96494..11c142d5 100644 --- a/src/dialog/registration.rs +++ b/src/dialog/registration.rs @@ -405,6 +405,7 @@ impl Registration { ); // Thanks to https://github.com/restsend/rsipstack/issues/32 + let contact_for_retry = contact.clone(); request.headers.unique_push(self.call_id.clone().into()); request.headers.unique_push(contact.into()); request.headers.unique_push(self.allow.clone().into()); @@ -446,10 +447,26 @@ impl Registration { if let Some(cred) = &self.credential { self.last_seq += 1; - // Handle authentication with the existing transaction - // The contact will be updated in the next registration cycle if needed tx = handle_client_authenticate(self.last_seq, &tx, resp, cred).await?; + // Update Contact in the retry request to use the discovered + // public address (rport/received from 401 Via header). + // handle_client_authenticate clones tx.original which has + // the stale Contact from the initial REGISTER. + if let Some(ref pa) = self.public_address { + let new_contact_uri = rsip::Uri { + auth: contact_for_retry.uri.auth.clone(), + scheme: Some(rsip::Scheme::Sip), + host_with_port: pa.clone(), + params: vec![], + headers: vec![], + }; + let mut new_contact = contact_for_retry.clone(); + new_contact.uri = new_contact_uri; + tx.original.headers.retain(|h| !matches!(h, rsip::Header::Contact(_))); + tx.original.headers.unique_push(new_contact.into()); + } + tx.send().await?; auth_sent = true; continue;