Conversation
A server INVITE transaction in Completed armed Timer K (T4, 5 s by default), which terminated it long before 64*T1: the 2xx was retransmitted for about 5 s instead of 32 s, and when no ACK came the dialog stayed in WaitAck (or Confirmed, for a re-INVITE) forever, with no event and no BYE. RFC 3261 §13.3.1.4: the UAS retransmits the 2xx starting at T1 and doubling up to T2 until the ACK arrives, on every transport (it can be lost at a later UDP hop); if none arrives within 64*T1, the session SHOULD be ended with a BYE. §17.2.1 likewise keeps a non-2xx in Completed for Timer H (64*T1); T4 (Timer I) applies only once the ACK has arrived. - Do not arm Timer K when a server INVITE enters Completed; Timer D (64*T1) already ends it, and Confirmed still uses T4. - Cap Timer G at T2 instead of 64*T1, with a new EndpointOption::t2 (default 4 s, the RFC value). - Start Timer G for a 2xx on reliable transports too, so one lost 2xx behind a TCP/TLS/WS hop does not end the call. A non-2xx is still retransmitted on unreliable transports only. - When the INVITE or re-INVITE transaction ends without an ACK after a 2xx, terminate the dialog with TerminatedReason::Timeout and send a BYE. Applied to InviteDialog and the deprecated ServerInviteDialog. The T2 cap is checked on the Timer G durations the transaction schedules rather than on wall-clock arrival times, which a loaded machine stretches.
tgeorge06
force-pushed
the
fix/uas-2xx-ack-timeout
branch
from
September 29, 2026 15:41
842bacd to
7e65add
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When a server INVITE transaction enters
Completedit arms Timer K = T4 (5 s by default). Timer K terminates the transaction well before 64·T1 (32 s), which causes two problems:WaitAck(initial INVITE) orConfirmed(re-INVITE) forever, with no state event and no BYE. The peer may consider the call up while no media flows, and the application cannot see that anything is wrong.In addition, Timer G doubled up to 64·T1 rather than T2, so later retransmissions were 8 s and 16 s apart.
Reproduction
New
src/dialog/tests/test_uas_ack_timeout.rsuses a raw UAC (UDP, and TCP for the reliable-transport case) and the usual UAS loop. Timers are short but keep the RFC's T4/T1 ratio: T1 = 20 ms, T4 = 200 ms, 64·T1 = 1.28 s.Terminated(Timeout), and a BYE must arrive no earlier than 64·T1, with the right Call-ID, From tag (the 2xx's To tag) and To tag.test_2xx_retransmission_interval_doubles_up_to_t2): witht2= 4·T1, the Timer G durations the transaction schedules double from T1 and stay at or below T2. It checks the scheduled durations rather than wall-clock arrival times, which a loaded machine stretches.test_2xx_over_tcp_is_retransmitted_until_the_ack): over TCP the 2xx is retransmitted until the ACK arrives. Onmainit is sent once (got 1 transmissions), so with the teardown below a single 2xx lost behind a later hop would end the call.Terminated(Timeout)and BYE.Confirmed.Fix
transaction.rs: a server INVITE enteringCompletedno longer arms Timer K. Timer D (64·T1) already ends the transaction; for a non-2xx it acts as Timer H, and for a 2xx it is the retransmission limit.Confirmedstill arms T4 (Timer I).transaction.rs: Timer G doubles up to the newEndpointOption::t2, which defaults to 4 s (the RFC value).transaction.rs: Timer G is also started for a 2xx on reliable transports (TCP/TLS/WS). RFC 3261 §13.3.1.4 has the UAS core retransmit the 2xx on every transport, because it can still be lost at a later UDP hop. A non-2xx is still retransmitted on unreliable transports only.dialog.rs: newDialogInner::end_session_without_ack. When the INVITE or re-INVITE transaction reachesTerminatedafter we sent a 2xx and no ACK came, it emitsTerminated(TerminatedReason::Timeout)and then sends a BYE (best effort; a failure is logged). It is called fromhandle_inviteandhandle_reinvitein bothInviteDialogand the deprecatedServerInviteDialog.Transaction::cleanuptakeslast_responseon termination.Nothing changes when the ACK arrives, when the INVITE is rejected or CANCELled, or when the endpoint shuts down (in that case the transaction is not
Terminated).Relation to #127 / #128
This addresses the defects reported in #127 that still reproduce on current
main(0.6.12): an ACK that arrives after T4 is swallowed and the dialog stays inWaitAck, and a 2xx that is never ACKed is retransmitted for only about T4 while the session is never ended.It deliberately keeps a server 2xx in
Completed. In rsipstack the transaction layer is the only place that retransmits the 2xx (the dialog layer'saccept()sends it once), so keeping it there gives the on-the-wire behaviour RFC 6026 §7.1 asks for — the 2xx is retransmitted until the ACK arrives or 64·T1 passes, and the ACK reaches the TU — without moving retransmission out of the transaction. It does not add the RFC 6026Acceptedstate; it is a smaller, targeted alternative to #128 for the observable problem.Compatibility / risk
EndpointOption::t2(default 4 s, the RFC 3261 value), added the same way as the existingt1/t4/t1x64fields. Code that buildsEndpointOptionwithout..Default::default()needs the new field; every construction in the repo already uses the default.Terminated(TerminatedReason::Timeout)plus a BYE after 64·T1, instead of staying up with no ACK.Checks (on
main@ 3ea35cd)cargo test→ 337 lib + 65 doc passed, 0 failed.rustfmt --checkclean on the changed files.