Skip to content

A dropped do_invite does nothing when the application already removed the dialog: no CANCEL, no Terminated, and a 2xx is never BYE'd #182

Closed

Description

@tgeorge06

Summary

Dropping the DialogLayer::do_invite future is the documented way to abandon an outgoing call, and since #162 / #171 the drop guard CANCELs the INVITE (after the first provisional when none has arrived yet), reports Terminated(UacCancel), and ACKs and BYEs a 2xx that crosses the CANCEL. The guard finds the dialog by removing it from the DialogLayer, though, and does nothing at all when it is no longer there.

So an application that calls the public DialogLayer::remove_dialog for the pending call (for example from its own hangup or cleanup path, with the id the Calling state reported) and then drops the do_invite future gets none of it:

  • no CANCEL is ever sent, so the callee keeps ringing until it gives up;
  • no Terminated is reported: the state channel just closes;
  • a 2xx to the INVITE is never BYE'd, so the callee is left in a session nobody owns. In Calling it is not even ACKed.

Expected

The same as for a dialog that is still in the layer. Removing the dialog from the registry does not end its INVITE:

  • RFC 3261 §9.1: "If no provisional response has been received, the CANCEL request MUST NOT be sent; rather, the client MUST wait for the arrival of a provisional response before sending the request." A CANCEL has no effect on a request that already got a final response.
  • RFC 3261 §13.2.2.4 / §15: the UAC ACKs every 2xx to its INVITE, and a UAC that does not want the session it established ends it with a BYE.
  • RFC 5407 §3.1.2 describes exactly this CANCEL / 2xx race: the 2xx is ACKed and the session is ended with a BYE.

What happens

At 3bdd74c:

  • do_invite (src/dialog/invitation.rs:675) registers the dialog under its initial id (lines 683-685) and creates DialogGuardForUnconfirmed (line 687). The Calling state reports that same id (src/dialog/invite_dialog.rs:271).
  • remove_dialog (src/dialog/dialog_layer.rs:509-513) removes the entry and runs on_remove, which only cancels the dialog's cancel_token (src/dialog/dialog.rs:1748-1752). The INVITE transaction keeps running inside do_invite.
  • When the do_invite future is then dropped, DialogGuardForUnconfirmed::drop (src/dialog/invitation.rs:192) starts with let Some(dlg) = self.dialog_layer_inner.dialogs.remove(&self.id.to_string()) else { return; }; (line 194). The entry is gone, so it returns, and the INVITE transaction is dropped with the guard.
  • A 2xx that arrives afterwards finds no transaction. After a provisional, the endpoint answers it with the placeholder ACK the dropped transaction left behind (src/transaction/transaction.rs:1590-1613, src/transaction/endpoint.rs:422-446); in Calling there is none, so not even an ACK is sent. Nothing sends a BYE.

Observed with a raw UDP peer (remove the dialog under the Calling id, drop the future, wait 300 ms, send 200 OK):

before any provisional: peer got [] after 200; states after drop []; layer len 0
after a 180:            peer got ["ACK"] after 200; states after drop []; layer len 0

No CANCEL was sent in either case.

A fix with tests 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