-
Notifications
You must be signed in to change notification settings - Fork 35
Description
BUG: ACK for 2xx INVITE fails when destination is a domain (UDP path lacks DNS resolution)
Summary
When sending an ACK for a 2xx final response to an outgoing INVITE,
rsipstack constructs the destination for the ACK based on the Contact header or Request-URI.
If that URI contains a DNS domain name, the ACK send path bypasses the usual SIP DNS logic and attempts to send UDP directly to a domain.
Since UdpConnection::send() cannot convert Host::Domain into SocketAddr, the ACK is never transmitted.
This produces errors like:
UDP send ERROR: Cannot convert domain to SocketAddr
and results in:
retransmissions of the 200 OK,
premature BYE or call teardown,
failure to establish a confirmed dialog state.
Reproduction Scenario
Client sends an outgoing INVITE.
Remote UAS replies with 2xx, with Contact: sip:servername:port.
send_ack() constructs ACK with Request-URI = the Contact URI.
ACK destination is overwritten with a SipAddr containing a domain instead of an IP.
SipConnection::send() for UDP tries to convert domain → SocketAddr without DNS resolution.
ACK send fails; no packet is emitted.
The INVITE path correctly uses TransportLayer::lookup() and DNS resolver.
The ACK path does not.
Root Cause
✔ INVITE & REGISTER use DNS resolution
Through TransportLayerInner::lookup() + DomainResolver.
❌ ACK path does not use DNS
Transaction::send_ack() bypasses the transport layer and sends directly to UdpConnection:
self.destination = destination_from_request(&ack);
If this destination contains a domain, UdpConnection::send() fails.
Minimal & Safe Fix (Option A)
Do not overwrite resolved destination for 2xx INVITE
The INVITE client transaction already resolved the correct IP address and stored it in self.destination.
Per RFC 3261 §13.2.2.4, the ACK for a 2xx response is an end-to-end request and may reuse this address.
Proposed patch (in transaction.rs → send_ack):
if let SipMessage::Request(ref req) = ack {
if let Some(resp) = self.last_response.as_ref() {
// Only update destination for non-2xx responses.
if resp.status_code.kind() != StatusCodeKind::Successful {
self.destination = destination_from_request(&req);
} else {
// For 2xx INVITE responses, keep the original destination (already resolved).
tracing::debug!(
"send_ack: keeping existing destination for 2xx, dest={:?}",
self.destination
);
}
}
}
This guarantees:
ACK uses the same resolved IP as INVITE,
avoids DNS lookup in hot paths,
preserves existing logic for error responses.
Alternative Fix (Option B)
Resolve domains in the UDP send path
Before calling UdpConnection::send(), detect domains and apply DNS resolution:
if let SipConnection::Udp(transport) = self {
let dest = if let Some(d) = destination {
match d.addr.host {
Host::Domain(_) => domain_resolver.resolve(d).await?,
_ => d.clone(),
}
} else {
None
};
transport.send(msg, dest.as_ref()).await
}
This ensures no UDP send ever fails due to Host::Domain.
Why This Should Be Fixed Upstream
SIP UAS commonly use domain names in Contact headers — this is RFC-compliant.
Dropping ACK breaks interoperability with many servers.
INVITE path already performs DNS, but ACK path does not — inconsistent behavior.
Fix is simple, localized, and does not change public API.
Zero performance impact.
Conclusion
Transaction::send_ack() for 2xx INVITE responses should not construct a raw domain-based destination for UDP sends.
Either:
Keep the resolved destination from INVITE (recommended), or
Perform DNS resolution before UDP send.
Happy to provide a PR with the patch.