Conversation
|
that seem to came up here miuda-ai/active-call#125 too |
Author
|
Thanks, yes, that's the same RFC 3261 §9.1 race. active-call#125 handles it in the application; this PR fixes it inside rsipstack's do_invite drop path (ACK + BYE the crossing 2xx), so applications get it without their own handling. |
Dropping the do_invite future while the call is ringing cancels the INVITE. The guard waited up to 2 s for the INVITE's final response and then dropped the transaction, but a 2xx found in that window was only logged, and a 2xx arriving later was ACKed by the detached transaction without anything ending the session. Either way the callee kept a confirmed dialog on dead air until it hung up. A CANCEL that crosses a 2xx has no effect on the INVITE; the UAC must ACK the 2xx and send a BYE (RFC 3261 §9.1, §15; RFC 5407 §3.1.2). Keep the INVITE transaction after the settle window until its final response (up to 64*T1) so a 2xx is ACKed, and send a BYE built from the 2xx's To tag, route set and Contact. Terminated(UacCancel) is still reported at the same point, and no Confirmed state is emitted for the abandoned call.
tgeorge06
force-pushed
the
fix/2xx-crossing-cancel
branch
from
October 5, 2026 18:07
31647d4 to
9cf1a0d
Compare
Author
|
Rebased onto 0.7.0. |
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 #158.
Problem
Dropping the
do_invitefuture after a provisional response cancels the INVITE:DialogGuardForUnconfirmedsends CANCEL and waits 2 s for the INVITE's final response, then drops the transaction. When the callee answers while the CANCEL is in flight, the 2xx is ACKed (by the transaction, or after the window by the detached transaction), but nothing sends a BYE, and the application no longer has a dialog to BYE. The callee stays in a confirmed call on dead air.Spec
Fix
DialogGuardForUnconfirmed::drop: keep the INVITE transaction past the 2 s window until its final response, with one absolute 64*T1 deadline measured from the cancel. The transaction ACKs the 2xx as before.InviteDialog::bye_2xx_after_cancel). The 2xx remote-target update is moved out ofprocess_inviteintoupdate_remote_target_from_2xx, shared by both paths.Terminated(UacCancel)is still emitted at the same point, and noConfirmedis emitted for the abandoned call. 487 and other final responses are unchanged.No public API change.
Contract / coverage
do_invitepath. An explicitInviteDialog::cancel()whiledo_invitestill runs does not go through this path and is unchanged: the crossing 2xx is returned to the caller, which owns the confirmed dialog.DialogGuardForUnconfirmed.do_invitecarries one Via, so its 2xx is still ACKed by the transaction; the multi-Via (proxied) path added in 3ea35cd is not involved.Tests
New
src/dialog/tests/test_cancel_2xx_race.rs(raw UDP peer, public API): 180, drop thedo_invitefuture, CANCEL, then the 200 to the INVITE (a) before the CANCEL's 200, (b) after it, (c) after the 2 s window. Each case checks that the ACK and the BYE carry the INVITE's Call-ID, our From tag and the 2xx's To tag, that the BYE uses a new CSeq and targets the 2xx Contact, that a retransmitted 2xx is re-ACKed without a second BYE, and that the state stream shows exactly oneTerminated(UacCancel)and noConfirmed.test_2xx_before_cancel_response_is_acked_and_byedtest_2xx_after_cancel_response_is_acked_and_byedtest_2xx_after_cancel_settle_window_is_acked_and_byedtest_cancel_answered_487_sends_no_bye(guard: 487 is ACKed, no BYE)Checks (on
main@ 3ea35cd)src/change reverted and the new test kept,cargo test --lib test_cancel_2xx_race→ 3 failed (timeout waiting for BYE), 1 passed (the 487 guard).cargo test→ 336 lib + 65 doc passed, 0 failed.rustfmt --checkclean on the changed files.Out of scope
do_invitebefore any provisional response: no CANCEL can be sent yet, and a later 2xx is not handled either. Separate change.process_invite; see Forked 2xx with a different To-tag is ACKed toward the first dialog's target; no dialog or BYE for it (RFC 3261 §13.2.2.4) #150).