Skip to content

fix: end the session when a UAS 2xx to an INVITE is never ACKed - #149

Open
tgeorge06 wants to merge 1 commit into
restsend:mainfrom
tgeorge06:fix/uas-2xx-ack-timeout
Open

tgeorge06 wants to merge 1 commit into
restsend:mainfrom
tgeorge06:fix/uas-2xx-ack-timeout

Conversation

@tgeorge06

Copy link
Copy Markdown

Problem

When a server INVITE transaction enters Completed it arms Timer K = T4 (5 s by default). Timer K terminates the transaction well before 64·T1 (32 s), which causes two problems:

  • A 2xx that is not ACKed is retransmitted for about 5 s instead of 32 s. A non-2xx likewise stops at T4 instead of Timer H.
  • When the ACK never comes, the dialog is stuck. It stays in WaitAck (initial INVITE) or Confirmed (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.rs uses real UDP, a raw UAC 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.

  • No ACK for the initial INVITE: the 2xx must be retransmitted with growing intervals until the last part of 64·T1. The dialog must then emit 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.
  • T2 cap: with t2 = 4·T1, every retransmission interval stays at or below T2 until 64·T1.
  • No ACK for a re-INVITE on an established call: the same retransmission, Terminated(Timeout) and BYE.
  • Control: when the ACK arrives, retransmissions stop, no BYE is sent and the dialog stays Confirmed.

Fix

  • transaction.rs: a server INVITE entering Completed no 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. Confirmed still arms T4 (Timer I).
  • transaction.rs: Timer G doubles up to the new EndpointOption::t2, which defaults to 4 s (the RFC value).
  • dialog.rs: new DialogInner::end_session_without_ack. When the INVITE or re-INVITE transaction reaches Terminated after we sent a 2xx and no ACK came, it emits Terminated(TerminatedReason::Timeout) and then sends a BYE (best effort; a failure is logged). It is called from handle_invite and handle_reinvite in both InviteDialog and the deprecated ServerInviteDialog.
  • For a re-INVITE, whether a 2xx was sent is read right after the final response goes out, because Transaction::cleanup takes last_response on 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.11): an ACK that arrives after T4 is swallowed and the dialog stays in WaitAck, 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's accept() 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 6026 Accepted state; it is a smaller, targeted alternative to #128 for the observable problem.

Compatibility / risk

  • New public field EndpointOption::t2 (default 4 s, the RFC 3261 value), added the same way as the existing t1 / t4 / t1x64 fields. Code that builds EndpointOption without ..Default::default() needs the new field; every construction in the repo already uses the default.
  • A UAS dialog whose 2xx is never ACKed now ends with Terminated(TerminatedReason::Timeout) plus a BYE after 64·T1, instead of staying up with no ACK.
  • A server INVITE transaction now lives up to 64·T1 (Timer D) after a final response instead of T4; nothing changes once the ACK arrives.

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; 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).
- 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant