Skip to content

A late ACK of a re-INVITE 2xx is routed to the next re-INVITE, and the call is ended with a BYE #165

Closed

Description

@tgeorge06

Summary

A server INVITE transaction that waits for the ACK of its 2xx is found by dialog only (EndpointInner::waiting_ack, one entry per dialog). When the ACK of re-INVITE N arrives after re-INVITE N+1 has been answered, it is routed to N+1's transaction. Since #155 that transaction ignores it (the CSeq does not match), so it confirms nothing. Re-INVITE N never sees its ACK: it retransmits its 2xx until 64*T1, and since #149 the dialog is then terminated with TerminatedReason::Timeout and a BYE. An established call is torn down although the peer ACKed every 2xx.

Reproduced on current main (8a89e9e) over UDP. The routing is the same on every transport.

Expected

  • RFC 3261 §13.2.2.4: "The sequence number of the CSeq header field MUST be the same as the INVITE being acknowledged, but the CSeq method MUST be ACK."
  • RFC 3261 §13.3.1.4: "Response retransmissions cease when an ACK request for the response is received." and "If the server retransmits the 2xx response for 64*T1 seconds without receiving an ACK, the dialog is confirmed, but the session SHOULD be terminated."
  • The 2xx ACK is sent by the UAC core, outside the INVITE transaction (§13.2.2.4, §17.1.1.2). A UAC can send its next re-INVITE right after that ACK, and over UDP the two can arrive in either order.

So the ACK with CSeq N must stop the retransmissions of the 2xx to the INVITE with CSeq N, whatever was received in between.

What happens

At 8a89e9e:

  • Transaction::transition (src/transaction/transaction.rs:1070-1074) inserts the transaction into waiting_ack under its dialog id when it enters Completed. A newer re-INVITE on the same dialog replaces the entry of the older one.
  • EndpointInner::on_received_message (src/transaction/endpoint.rs:382) routes every ACK to waiting_ack.get(&dialog_id), whatever its CSeq.
  • The CSeq check added by fix: ignore an ACK whose CSeq does not match the server INVITE #155 (src/transaction/transaction.rs:751-772) then drops the ACK in the newer transaction. That check is right, but the older transaction is never reached.
  • Removal is by dialog too, so whichever transaction finishes first also removes the other one's route: transaction.rs:1093 (Confirmed), transaction.rs:1168 (cleanup) and endpoint.rs:397 (absorbed ACK).

Sequence: INVITE 1 / 200 / ACK 1, then re-INVITE 2 answered 200, re-INVITE 3 answered 200, then ACK 2, then ACK 3. On main the 200 to CSeq 2 is still retransmitted after ACK 2, and after 64*T1 the UAS sends a BYE and the dialog reports Terminated(_, Timeout) (test below, with its first assertion disabled). With the default timers that is 32 s into a call that is working.

A fix with a test is in the linked PR.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions