Repository navigation
Conversation
… 200 A CANCEL shares the server INVITE's transaction key. In Accepted (a 2xx awaiting its ACK, RFC 6026) and in Confirmed it was answered 481, and once the INVITE transaction had ended the cached INVITE response was replayed as the answer to the CANCEL. Answer it 200 with CSeq CANCEL in Accepted and Confirmed, as in Completed, without passing it to the TU, and in the finished_transactions path when the cached response is final. An INVITE dropped with only a provisional, or no INVITE at all, still gets 481. The 200 carries the To of the INVITE's last response (RFC 3261 9.2).
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.
Fixes #198.
Thanks to @beija-wq for the report. The analysis and the suggested fix in the issue are exact; the reproduction program prints
not reproducedwith this PR (REPRODUCED - 2/2onmain).Problem
A server-side CANCEL has the INVITE's transaction key (
src/transaction/key.rs:41-43), so it lands on the INVITE transaction while it lives and on itsfinished_transactionsentry after it ended. Neither answers it correctly once the INVITE has a final response:Transaction::on_received_requesttakes the_arm and answers 481 (src/transaction/transaction.rs:867-884at currentmain, 6fd9414). Only Trying/Proceeding and Completed are handled (lines 863-866). 0.6.x answered 200 here.EndpointInner::on_received_messagefinds thefinished_transactionsentry under the CANCEL's key and re-sends the cached INVITE response as the answer (src/transaction/endpoint.rs:412-419). The CANCEL gets a200 OKwithCSeq: 1 INVITE(or a copy of a 480, or of a 180 when the TU dropped an unanswered INVITE), its client transaction never completes, and the UAC retransmits it until Timer F.Expected
RFC 3261 §9.2:
RFC 6026 §7.1 keeps the server INVITE transaction alive in "Accepted" after a 2xx; it does not mention CANCEL, so the §9.2 rule for a transaction with a final response applies.
Fix
transaction.rs: Accepted and Confirmed join Completed. The CANCEL gets 200, is not passed to the TU, and the INVITE transaction is not touched (the 2xx keeps being retransmitted until its ACK, which still ends the transaction).endpoint.rs,finished_transactionspath: a CANCEL is never answered with the cached message. If the cached message is a final response it gets 200. If it is a provisional (the TU dropped the INVITE without answering it), it falls through to the existing "no transaction" arm and gets 481, as with no INVITE at all. INVITE retransmissions and ACKs take the cached path exactly as before.EndpointInner::make_cancel_response,src/transaction/message.rs), so the 200 to the CANCEL has the To tag of the INVITE's final response. The live-transaction path does the same in Proceeding (the To of the last provisional). The 481 arm of the endpoint now goes through a small sharedreply_cancelhelper, with the samemake_response,before_send(.., None)and Via destination as before.+63 / -23 lines outside the test, over
endpoint.rs,transaction.rsandmessage.rs.Unchanged on purpose:
finished_transactionsentry answers a CANCEL with 200 for as long as it is kept (64*T1 after the transaction ended,endpoint.rs:546-555), as it already answers INVITE retransmissions for that time. Strictly, RFC 3261 has no transaction left by then; tracking a deadline per entry would add state for a case where 200 and 481 both just complete the UAC's CANCEL transaction and neither touches the INVITE._arm (no state left that a CANCEL can match) is not changed.finished_transactions) and fix(transaction): add the 2xx ACK route before sending the 2xx #193 (the ACK route added before the send) behave as before; the CANCEL takes no ACK path.Contract / coverage
What a CANCEL for a server INVITE is answered with (UDP and TCP alike):
CSeq: 1 INVITE)CSeq: 1 INVITE)In no row does the CANCEL reach the TU or change the INVITE transaction once a final response was sent.
Tests
src/transaction/tests/test_server_cancel.rs:test_cancel_answers_by_invite_state, a raw UDP or TCP peer against an endpoint, one row per line of the table above, over both transports. Every row sends the CANCEL twice (a retransmission; in Proceeding the second one after the 487). It checks the code andCSeqof every response the peer gets, that only genuine INVITE responses carryCSeq: INVITE(the 487, and the final response retransmitted while the transaction lives), the To tag of each 200 to a CANCEL, that the CANCEL never reaches the TU after a final response and leaves the transaction state alone, and that in Accepted the ACK still ends the transaction after the CANCEL.On
main(6fd9414) it fails on the first row:With the assertions relaxed to print each row,
maingives: accepted481 CANCEL, acked200 INVITEtwice, confirmed481 CANCEL, ended after a 480480 INVITEtwice, ended after a 180180 INVITEtwice, on UDP and TCP; the Proceeding, Completed and no-INVITE rows match. Without the To copy, the To-tag assertions fail. The test passed 10 runs in a row.The issue's reproduction program (UDP, default
EndpointOption, the dialog layer's usual UAS loop):Checks
cargo test --features bench: 421 lib tests passed, 0 failed (420 onmainplus the new one), and 65 doc tests passed. Plaincargo testpasses too.cargo check --no-default-features --features platform-embassy: no warnings, as onmain.cargo clippy --features bench --all-targets: the same output as onmain, none in the changed code. (Onmainit stops at aclippy::never_looperror insrc/dialog/tests/test_refer_notify.rs:98, unrelated to this PR; with-A clippy::never_loopthe warnings are the same as onmain.)cargo fmt --check: clean.