RFC 5407

Example Call Flows of Race Conditions in the Session Initiation Protocol (SIP)

πŸ“… published December 2008, from ietf

RFC 5407: Example Call Flows of Race Conditions in the Session Initiation Protocol (SIP) πŸ’–

okay bestie, this one is the BCP 147 (a Best Current Practice πŸŽ€). It came out in December 2008 from the IETF's Network Working Group, and it's written by five people: M. Hasebe, J. Koshiko, Y. Suzuki, T. Yoshikawa, and P. Kyzivat. Hasebe and Koshiko are from NTT-east Corporation, Suzuki is from NTT Corporation, Yoshikawa is from NTT-east Corporation, and Kyzivat is from Cisco Systems, Inc. ✨ The header doesn't list anything it obsoletes or updates, so it stands on its own 🧚

Status of This Memo 🌸

This document is an Internet Best Current Practice for the Internet Community, and it's asking for discussion and suggestions for improvements 🫢 Distribution of this memo is unlimited, so share it with everyone πŸ’…

Copyright (c) 2008 IETF Trust and the persons identified as the document authors. All rights reserved. This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (http://trustee.ietf.org/license-info) as they were in effect on the date it was published. Please read these carefully, because they explain your rights and restrictions for this document πŸ“œ

Abstract ⭐

So the vibe of this doc: it gives example call flows of race conditions in the Session Initiation Protocol (SIP). Race conditions are genuinely confusing and really hard to thwart 😭 This doc shows the best practices for handling them. The elements in these call flows are SIP User Agents and SIP Proxy Servers, and it includes call flow diagrams and message details πŸ”₯

Table of Contents πŸ“š

1.  Overview . . . . . . . . . . . . . . . . . . . . . . . . . . .  3
  1.1.  General Assumptions  . . . . . . . . . . . . . . . . . . .  3
  1.2.  Legend for Message Flows . . . . . . . . . . . . . . . . .  3
  1.3.  SIP Protocol Assumptions . . . . . . . . . . . . . . . . .  4
2.  The Dialog State Machine for INVITE Dialog Usage . . . . . . .  5
3.  Race Conditions  . . . . . . . . . . . . . . . . . . . . . . . 10
  3.1.  Receiving Message in the Moratorium State  . . . . . . . . 11
    3.1.1.  Callee Receives Initial INVITE Retransmission
            (Preparative State) While in the Moratorium State  . . 11
    3.1.2.  Callee Receives CANCEL (Early State) While in the
            Moratorium State . . . . . . . . . . . . . . . . . . . 13
    3.1.3.  Callee Receives BYE (Early State) While in the
            Moratorium State . . . . . . . . . . . . . . . . . . . 15
    3.1.4.  Callee Receives re-INVITE (Established State)
            While in the Moratorium State (Case 1) . . . . . . . . 17
    3.1.5.  Callee Receives re-INVITE (Established State)
            While in the Moratorium State (Case 2) . . . . . . . . 22
    3.1.6.  Callee Receives BYE (Established State) While in
            the Moratorium State . . . . . . . . . . . . . . . . . 26
  3.2.  Receiving Message in the Mortal State  . . . . . . . . . . 28
    3.2.1.  UA Receives BYE (Established State) While in the
            Mortal State . . . . . . . . . . . . . . . . . . . . . 28
    3.2.2.  UA Receives re-INVITE (Established State) While in
            the Mortal State . . . . . . . . . . . . . . . . . . . 30
    3.2.3.  UA Receives 200 OK for re-INVITE (Established
            State) While in the Mortal State . . . . . . . . . . . 32
    3.2.4.  Callee Receives ACK (Moratorium State) While in
            the Mortal State . . . . . . . . . . . . . . . . . . . 35
  3.3.  Other Race Conditions  . . . . . . . . . . . . . . . . . . 36
    3.3.1.  Re-INVITE Crossover  . . . . . . . . . . . . . . . . . 36
    3.3.2.  UPDATE and re-INVITE Crossover . . . . . . . . . . . . 40
    3.3.3.  Receiving REFER (Established State) While in the
            Mortal State . . . . . . . . . . . . . . . . . . . . . 45
4.  Security Considerations  . . . . . . . . . . . . . . . . . . . 46
5.  Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . 46
6.  References . . . . . . . . . . . . . . . . . . . . . . . . . . 47
  6.1.  Normative References . . . . . . . . . . . . . . . . . . . 47
  6.2.  Informative References . . . . . . . . . . . . . . . . . . 47
Appendix A.  BYE in the Early Dialog . . . . . . . . . . . . . . . 48
Appendix B.  BYE Request Overlapping with re-INVITE  . . . . . . . 49
Appendix C.  UA's Behavior for CANCEL  . . . . . . . . . . . . . . 52
Appendix D.  Notes on the Request in the Mortal State  . . . . . . 54
Appendix E.  Forking and Receiving New To Tags . . . . . . . . . . 54

1. Overview ✨

The call flows in this document were developed while designing a SIP IP communications network 🌈 They're examples of race conditions, which come from changes in dialog states, mostly the changes that happen during session establishment after an INVITE has been sent πŸ’œ

When you implement SIP, all sorts of complicated situations can pop up. So it helps implementors to have examples of recommended terminal and server behavior πŸ“

This document spells out how SIP User Agent (UA) behavior works when messages cross each other as race conditions. Once the behavior under race conditions is clear, implementations stop interpreting things differently, and that should help interoperability 🫢

The authors really hope this document helps SIP implementors, designers, and protocol researchers, and that it helps them reach the goal of a standard implementation of RFC 3261 [1] πŸŽ€

These call flows are based on version 2.0 of SIP, which is defined in RFC 3261 [1], with SDP usage as described in RFC 3264 [2] πŸ¦‹

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14, RFC 2119 [3] πŸ’…

1.1. General Assumptions 🌸

There are a bunch of architectural, network, and protocol assumptions underneath the call flows in this document 🧚 These assumptions are not requirements. They're listed here so you can keep them in mind and understand the call flow examples better ✨

These flows don't assume any specific underlying transport protocols, like TCP, TLS, and UDP. For the transport issues in SIP, check the discussion in RFC 3261 [1] πŸ”₯

1.2. Legend for Message Flows πŸ“š

Dashed lines (---) and slash lines (/, ) show signaling messages that are mandatory to the call scenario. (X) shows where signaling messages cross over. (->x, x<-) means the packet is lost. The arrow shows which way the message flow is going. Double dashed lines (===) show media paths between network elements πŸ‘‘

The messages are labeled F1, F2, and so on in the figures. Those numbers are how you find the message details that come after each figure. Comments in the message details look like this:

/* Comments.  */

1.3. SIP Protocol Assumptions 🫧

This document doesn't prescribe the flows exactly as they're drawn. Instead, it shows the principles of best practice. These are best practice usages (orderings, syntax, choosing features for the purpose, or handling errors) of SIP methods, headers, and parameters. Note: the flows in this document must not be copied as-is by implementors, because extra annotations were added to the document to make it easier to explain 😭 To sum up, the procedures here are well-reviewed examples of SIP usage, which show best common practice according to IETF consensus πŸ’–

For the sake of simplicity when reading and editing, some of the examples are different from actual SIP messages. For instance, Call-IDs are often copied, CSeq often starts at 1, header fields usually appear in the same order, usually only the minimum required header field set is shown, and other headers that would normally be included, like Accept, Allow, etc., are left out πŸ™ˆ

Actors:

Element     Display Name  URI                            IP Address
-------     ------------  ---                            ----------

User Agent  Alice         sip:alice@atlanta.example.com  192.0.2.101
User Agent  Bob           sip:bob@biloxi.example.com     192.0.2.201
User Agent  Carol         sip:carol@chicago.example.com  192.0.2.202
Proxy Server              ss.atlanta.example.com         192.0.2.111

The word "session" is used in this document the same way it's used in Sections 13-15 of RFC 3261 [1] (which is a bit different from how RFC 3261 defines the term). RFC 5057 [6] introduces another term, "invite dialog usage", which is defined more precisely. The "session" used here is almost, but not quite, the same as "invite dialog usage". The two differ on when the state ends: the session ends earlier, when BYE is sent or received πŸ¦‹

2. The Dialog State Machine for INVITE Dialog Usage πŸ‘‘

Race conditions happen when the dialog state of the receiving side is different from the dialog state of the sending side πŸ”₯

For example, a race condition happens when a UAC (User Agent Client) sends a CANCEL in the Early state while the UAS (User Agent Server) is moving from the Early state to the Confirmed state by sending a 200 OK to an initial INVITE (called "ini-INVITE" from here on). To help you understand how a UA behaves in race conditions, the DSM (dialog state machine) for the INVITE dialog usage is shown below πŸ’…

The DSM breaks down the dialog state from RFC 3261 [1] into smaller internal states, which makes the UA's behavior in race conditions clearer 🌈 The state before a dialog is established is called the Preparative state. The Confirmed state splits into two substates, the Moratorium and Established states, and the Terminated state splits into the Mortal and Morgue states πŸ’œ The messages that trigger transitions between these states are shown with arrows. In this figure, messages that don't affect state transitions are left out 🧚

Below are the DSMs, first for the caller and then for the callee πŸŽ€

part 1 of 11 done, bestie 🫢 keep going for part 2!
    INV +-----------------------------------------------+
    --->|                 Preparative                   |
        +-----------------------------------------------+
          |                    |                      |
          | 3xx-6xx            | 1xx-tag              | 2xx
          |                    |                      |
          |                    |        1xx-tag       |
          |                    V        w/new tag     |
          |         +-----------------+  [new DSM]    |
          | 3xx-6xx |                 |   | (new DSM  |
          +<--------|      Early      |   |  instance |
          |         |                 |<--+  created) |
          |         +-----------------+               |
          |            |             |                |  2xx w/new tag
          |            | BYE         | 2xx            |   [new DSM]
          |            |             +------------>+<-+      | (new DSM
          |            |                           |         |  instance
    +-----C------------C-----+         +-----------C------+  |  created)
    |     | Terminated |     |         | Confirmed |      |  |
    |     |            +<----C---------|           |      |  |
    |     |            |     | BYE(sr) |           |      |  |
    |     |            V     |         |           V      |  |
    | 2xx |  +-----------+   |         |   +-----------+  |  |
    | +---C--|           |---C-+       |   |           |  |  |
    | |   |  |   Mortal  |   | | BYE(r)|   | Moratorium|<-C--+
    | +---C->|           |<--C-+       |   |           |  |
    | ACK |  +-----------+   |         |   +-----------+  |
    |     |    |             |         |         |        |
    |     |    | Timeout     |         |         | ACK    |
    |     |    |             |         |         |        |
    |     V    V             |         |         V        |
    |   +---------------+    |         |   +-----------+  |
    |   |               |    |         |   |           |--C-+
    |   |     Morgue    |    |         |   |Established|  | | 2xx,ACK
    |   |               |    |         |   |           |<-C-+
    |   +---------------+    |         |   +-----------+  |
    |                        |         |                  |
    +------------------------+         +------------------+

    (r): indicates that only reception is allowed.
         Where (r) is not used as an indicator, "response" means
         receive, and "request" means send.
    (sr): indicates that both sending and reception are allowed.

              Figure 1: DSM for INVITE dialog usage (caller)

Okay, so Figure 1 is the caller's DSM for INVITE dialog usage πŸ¦‹ Read it like a little map of where the dialog can go. ✨

The caller MAY send a BYE in the Early state, even though this behavior is not recommended πŸ™ˆ A BYE sent in the Early state terminates the early dialog using a specific To tag. So when a proxy is doing forking, the BYE can only end the early dialog with one particular UA. πŸ’… If the caller wants to terminate all early dialogs instead of just the one with a particular UA, it needs to send CANCEL, not BYE. Still, it's not illegal to send BYE in the Early state to end a specific early dialog if that's what the caller is going for 🫢 Moreover, until the caller receives a final response and terminates the INVITE transaction, the caller MUST be prepared to establish a dialog by receiving a new response to the INVITE, even if it has already sent a CANCEL or BYE and terminated the dialog (see Appendix A). Not a detail you wanna skip, fr fr 😭

    INV +-----------------------------------------------+
    --->|                 Preparative                   |
        +-----------------------------------------------+
          |                         |                 |
          | 3xx-6xx                 | 1xx-tag         | 2xx
          |                         |                 |
          |                         V                 |
          |         +------------------+              |
          | 3xx-6xx |                  |              |
          +<--------|      Early       |              |
          |         |                  |              |
          |         +------------------+              |
          |            |             |                |
          |            |BYE/487(INV) | 2xx            |
          |            |             +------------>+<-+
          |            |                           |
    +-----C------------C-----+         +-----------C------+
    |     | Terminated |     |         | Confirmed |      |
    |     |            +<----C---------|           |      |
    |     |            |     | BYE(sr) |           |      |
    |     |            V     |         |           V      |
    |     | +------------+   |         |   +-----------+  |
    |     | |            |---C-+       |   |           |--C-+
    |     | |   Mortal   |   | | BYE   |   | Moratorium|  | | 2xx
    |     | |            |<--C-+       |   |           |<-C-+ if ACK not
    |     | +------------+   |         |   +-----------+  |   received
    |     |   |              |         |         |        |
    |     |   | Timeout      |         |         | ACK    |
    |     |   |              |         |         |        |
    |     V   V              |         |         V        |
    |   +---------------+    |         |   +-----------+  |
    |   |               |    |         |   |           |  |
    |   |     Morgue    |    |         |   |Established|  |
    |   |               |    |         |   |           |  |
    |   +---------------+    |         |   +-----------+  |
    |                        |         |                  |
    +------------------------+         +------------------+

     (sr): indicates that both sending and reception are allowed.
          Where (sr) is not used as an indicator, "response" means send,
          and "request" means receive.

              Figure 2: DSM for INVITE dialog usage (callee)

Figure 2 is the callee's side of the same story, the DSM for INVITE dialog usage πŸ’– This one doesn't draw the state transition for CANCEL requests. A CANCEL request doesn't cause a dialog state transition on its own. Instead, the callee terminates the dialog and triggers the transition by sending a 487 right after it receives the CANCEL ✨ How the UA behaves when it gets a CANCEL is explained further in Appendix C, so go peek there if you're curious πŸ‘€

Here's how the UA acts in each state, bestie πŸŽ€

Preparative (Pre): The Preparative state is in effect until the early dialog is established by sending or receiving a provisional response with a To tag, after an ini-INVITE is sent or received. The dialog doesn't exist yet in this state 🌸 If the UA sends or receives a 2xx response, the dialog moves from Preparative to the Moratorium state, which is a substate of the Confirmed state. And if the UA sends or receives a 3xx-6xx response, the dialog goes to the Morgue state, which is a substate of the Terminated state. Sending an ACK for a 3xx-6xx response and retransmissions of 3xx-6xx aren't shown on the DSMs, because the INVITE transaction sends them anyway 🧚

Early (Ear): The early dialog gets established by sending or receiving a provisional response, except 100 Trying. The early dialog exists even though the dialog itself doesn't exist in this state yet, which is giving paradox energy 🌈 The dialog moves from Early to the Moratorium state (a substate of Confirmed) by sending or receiving a 2xx response. It goes to the Morgue state (a substate of Terminated) by sending or receiving a 3xx-6xx response. Sending an ACK for a 3xx-6xx response and retransmissions of 3xx-6xx aren't shown on this DSM either, because they're automatically handled on the transaction layer and don't change the dialog state. The UAC may send a CANCEL in the Early state. It may also send a BYE, although that's not recommended. The UAS may send a 1xx-6xx response. Sending or receiving a CANCEL request doesn't have a direct influence on the dialog state, and the UA's behavior when it gets a CANCEL is explained further in Appendix C πŸ’…

Confirmed (Con): Sending or receiving a 2xx final response establishes a dialog, and the dialog starts in this state ✨ The Confirmed state moves to the Mortal state (a substate of Terminated) by sending or receiving a BYE request. Confirmed has two substates, Moratorium and Established, and they're different when it comes to which messages UAs are allowed to send. Two vibes in one state, iykyk πŸ¦‹

Moratorium (Mora): Moratorium is a substate of Confirmed and inherits its behavior. It moves to the Established state by sending or receiving an ACK request. The UAC may send an ACK, and the UAS may send a 2xx final response πŸ“

Established (Est): Established is a substate of Confirmed and inherits its behavior too. Both caller and callee may send various messages that influence a dialog πŸ’œ The caller supports the transmission of ACK to the retransmission of a 2xx response to an ini-INVITE.

Terminated (Ter): Terminated is split into two substates, Mortal and Morgue, to cover the behavior when a dialog is being terminated. In this state, the UA holds information about the dialog that's being terminated πŸ₯€

Mortal (Mort): The caller and callee enter the Mortal state by sending or receiving a BYE. The UA MUST NOT send any new requests within the dialog, because there is no dialog anymore. (Here, the new requests don't include ACK for 2xx or BYE for 401 or 407, as further explained in Appendix D below.) In the Mortal state, BYE can be accepted, and the other messages in the INVITE dialog usage get responded to with an error. This covers the case where a caller and a callee swap reports about the session while it's being terminated πŸ“ So the UA keeps dialog information for internal processing, but the dialog shouldn't be externally visible. The UA stops managing its dialog state and switches to the Morgue state when the BYE transaction is terminated πŸŒ™

Morgue (Morg): The dialog no longer exists in this state. Signaling that influences a dialog isn't sent or received here. (A dialog is literally terminated, bestie πŸ’€) The caller and callee enter the Morgue state when the BYE or INVITE transaction terminates ⭐


3. Race Conditions ✨

This section details a race condition between two SIP UAs, Alice and Bob πŸ‘―β€β™€οΈ Alice (sip:alice@atlanta.example.com) and Bob (sip:bob@biloxi.example.com) are assumed to be SIP phones or SIP-enabled devices. Only significant signaling gets illustrated πŸ“ž Dialog state transitions caused by sending or receiving SIP messages are shown, and race conditions are marked with '*race*'. (For abbreviations of the dialog state transitions, refer to Section 2 πŸ’…) '*race*' marks the exact moment a race condition happens. It's giving drama 🎭

Examples of race conditions are described below 🫢

3.1. Receiving Message in the Moratorium State

Okay, so this part shows some examples of call flow race conditions when a UA receives messages from other states while it's in the Moratorium state πŸ¦‹

3.1.1. Callee Receives Initial INVITE Retransmission (Preparative State) While in the Moratorium State ✨

   State  Alice                               Bob  State
          |                                     |
          |            ini-INVITE F1            |
          |------------------------------------>|
     Pre  |         180 F2(Packet loss)         |  Pre
          |            x<-----------------------|
          |                                     |  Ear
          | ini-INVITE F4(=F1)           200 F3 |
          |------------------     --------------|
          |                   \ /               |  Mora
          |                    X                |
          |                   / \               |
          |<-----------------     ------------->|  *race*
    Mora  |                ACK F5               |
          |------------------------------------>|
     Est  |                                     |  Est
          |                                     |

okay so this is the "my 180 got yeeted into the void" scenario πŸ’€ All of Bob's provisional responses to the initial INVITE (ini-INVITE F1) get lost, so Alice retransmits her ini-INVITE as F4. At the exact same moment, Bob's UAS sends a 200 OK (F3) to the ini-INVITE and closes out the INVITE server transaction, according to Section 13.3.1.4 of RFC 3261 [1].

BUT here's the plot twist 🫒 It's been reported that closing the INVITE server transaction when you send a 200 OK is an essential correction to SIP [7]. So F3 does not terminate the INVITE server transaction, and F4 MUST be handled properly as a retransmission. 🫢

RFC 3261 [1] doesn't spell out whether the UAS retransmits the 200 when it gets the ini-INVITE retransmission. If you think about 200 retransmissions driven by timers (the transaction user, or TU, keeps resending 200 based on T1 and T2 until it gets an ACK), then according to Section 13.3.1.4 of RFC 3261 [1], it seems unnecessary to resend the 200 when the UAS receives the ini-INVITE retransmission. (For implementation, it doesn't matter whether the UAS sends the retransmitted 200, since the 200 doesn't cause any problem. πŸ’…)

Message Details

   F1 INVITE Alice -> Bob

   F2 180 Ringing Bob -> Alice

   /* 180 response is lost and does not reach Alice.  */

   F3 200 OK Bob -> Alice

   /* According to Section 13.3.1.4 of RFC 3261 [1], the INVITE server
      transaction is terminated at this point.  However, this has been
      reported as an essential correction to SIP, and the UAS MUST
      correctly recognize the ini-INVITE (F4) as a retransmission.  */

   F4 INVITE (retransmission) Alice -> Bob

   /* F4 is a retransmission of F1.  They are exactly the same INVITE
      request.  For UAs that have not dealt with the correction [7] (an
      INVITE server transaction is terminated when sending 200 to
      INVITE), this request does not match the transaction as well as
      the dialog since it does not have a To tag.  However, Bob must
      recognize the retransmitted INVITE correctly, without treating it
      as a new INVITE.  */

   F5 ACK Alice -> Bob

3.1.2. Callee Receives CANCEL (Early State) While in the Moratorium State πŸ¦‹

   State  Alice                        Bob  State
          |                              |
          |          INVITE F1           |
          |----------------------------->|
     Pre  |       180 Ringing F2         |  Pre
          |<-----------------------------|
     Ear  |                              |  Ear
          |CANCEL F3       200(INVITE) F4|
          |------------     -------------|
          |             \ /              |  Mora
          |              X               |
          |             / \              |
          |<-----------     ------------>|  *race*
    Mora  |                              |
          | ACK F6         200(CANCEL) F5|
          |------------     -------------|
     Est  |             \ /              |
          |              X               |
          |             / \              |
          |<-----------     ------------>|
          |                              |  Est
          |       One Way RTP Media      |
          | (Two Way RTP Media possible) |
          |<=============================|
          |            BYE F7            |
          |----------------------------->|
    Mort  |            200 F8            |  Mort
          |<-----------------------------|
          | ^                          ^ |
          | | Timer K                  | |
          | V                          | |
    Morg  |                    Timer J | |
          |                            V |
          |                              |  Morg
          |                              |

this is the race where Bob's UAS gets an Early message, CANCEL, while it's in Moratorium 🌊 Alice sends a CANCEL, and at the exact same time Bob sends a 200 OK to the initial INVITE. As we just saw, RFC 3261 [1] says an INVITE server transaction is supposed to be terminated by a 200 response, but that's been corrected in [7].

This section covers the case where the INVITE server transaction is not terminated by a 200 response to the INVITE request. Here there is an INVITE transaction that the CANCEL request matches, so a 200 response to the CANCEL is sent. That 200 just means the next hop received the CANCEL request. (A successful CANCEL (200) does not mean the INVITE was actually canceled. πŸ™ˆ) When a UAS has not dealt with the correction [7], the UAC MAY receive a 481 response to the CANCEL, since there's no transaction the CANCEL request matches. That 481 simply means there's no matching INVITE server transaction, and the CANCEL is not sent to the next hop. Regardless of whether the CANCEL succeeds or fails, Alice checks the final response to the INVITE. If she gets a 200 to the INVITE request, she immediately sends a BYE and terminates the dialog. (See Section 15, RFC 3261 [1].)

From the time F1 is received by Bob until Bob sends F8, media may be flowing one way, from Bob to Alice. Once Alice receives an answer from Bob, media may also flow from Alice to Bob. However, once Alice has decided to cancel the call, she presumably won't send media, so practically speaking the media stream stays one way. 🎧

Message Details

   F1 INVITE Alice -> Bob

   F2 180 Ringing Bob -> Alice

   F3 CANCEL Alice -> Bob

   /* Alice sends a CANCEL in the Early state.  */

   F4 200 OK (INVITE) Bob -> Alice

   /* Alice receives a 200 to INVITE (F1) in the Moratorium state.
      Alice has the potential to send as well as receive media, but in
      practice will not send because there is an intent to end the
      call.  */

   F5 200 OK (CANCEL) Bob -> Alice

   /* 200 to CANCEL simply means that the CANCEL was received.  The 200
      response is sent, since this case assumes the correction [7] has
      been made.  If an INVITE server transaction is terminated
      according to the procedure stated in RFC 3261 [1], the UAC MAY
      receive a 481 response instead of 200.  */

   F6 ACK Alice -> Bob

   /* INVITE is successful, and the CANCEL becomes invalid.  Bob
      establishes RTP streams.  However, the next BYE request
      immediately terminates the dialog and session.  */

   F7 BYE Alice -> Bob

   F8 200 OK Bob -> Alice

3.1.3. Callee Receives BYE (Early State) While in the Moratorium State πŸ’œ

   State  Alice                          Bob  State
          |                                |
          |         ini-INVITE F1          |
          |------------------------------->|
     Pre  |            180 F2              |  Pre
          |<-------------------------------|
     Ear  |                                |  Ear
          |    BYE F4        200(INVITE) F3|
          |-------------     --------------|
    Mort  |              \ /               |  Mora
          |               X                |
          |              / \               |
          |<------------     ------------->|  *race*
          |                                |  Mort
          |    ACK F5         200(BYE) F6  |
          |-------------     --------------|
          |              \ /            ^  |
          |               X             |  |
          |              / \            |  |
          |<------------     ------------->|
          | ^                           |  |
          | | Timer K                   |  |
          | V                           |  |
    Morg  |                     Timer J |  |
          |                             V  |
          |                                |  Morg
          |                                |

this is the race where Bob's UAS gets an Early message, BYE, while it's in Moratorium πŸ“ Alice sends a BYE in the Early state, and Bob sends a 200 OK to the initial INVITE request at the same time. Bob receives the BYE in the Confirmed dialog state, even though Alice sent it in the Early state. (As explained in Section 2 and Appendix A, this behavior is not recommended.) When a proxy is performing forking, the BYE can only terminate the early dialog with one particular UA. If the caller wants to terminate all early dialogs instead of just the one with a particular UA, it needs to send CANCEL, not BYE. However, it is not illegal to send BYE in the Early state to terminate a specific early dialog if that's what the caller intends. 🧚

The BYE works normally even if it's received after the INVITE transaction terminates, because BYE differs from CANCEL and is sent not to the request but to the dialog. Alice enters the Mortal state on sending the BYE and stays Mortal until the Timer K timeout occurs. In the Mortal state, the UAC does not establish a session even though it receives a 200 response to the INVITE. Even so, the UAC sends an ACK to the 200 in order to complete the INVITE transaction. The ACK is always sent to complete the three-way handshake of the INVITE transaction (further explained in Appendix D below). 🫢

Message Details

   F1 INVITE Alice -> Bob

   F2 180 Ringing Bob -> Alice

   F3 200 OK (ini-INVITE) Bob -> Alice

   F4 BYE Alice -> Bob

   /* Alice transitions to the Mortal state upon sending BYE.
      Therefore, after this, she does not begin a session even though
      she receives a 200 response with an answer.  */

   F5 ACK Alice -> Bob

   F6 200 OK (BYE) Bob -> Alice

3.1.4. Callee Receives re-INVITE (Established State) While in the Moratorium State (Case 1) πŸŽ€

  State  Alice                          Bob  State
         |                                |
         |    ini-INVITE w/offer1 F1      |
         |------------------------------->|
    Pre  |             180 F2             |  Pre
         |<-------------------------------|
    Ear  |                                |  Ear
         |   200(ini-INV) w/answer1 F3    |
         |<-------------------------------|
   Mora  |       ACK F4(packet loss)      |  Mora
         |-------------------->x          |
    Est  |                                |
         | re-INVITE F6      200 F5(=F3)  |
         |   w/offer2         w/answer1   |
         |-------------     --------------|
         |              \ /               |
         |               X                |
         |              / \               |
         |<------------     ------------->|  *race*
         |                  200(re-INV) F8|
         | ACK F7(=F4)        w/answer2   |
         |-------------     --------------|
         |              \ /               |
         |               X                |
         |              / \               |
         |<------------     ------------->|
         |         ACK (re-INV) F9        |  Est
         |------------------------------->|
         |                                |
         |                                |

okay so this scenario is all about the race condition πŸŽοΈπŸ’¨ that happens when a UAS is still in the Moratorium state and a UAC sends it a re-INVITE while it's already in the Established state. it's giving chaotic crossover energy, iykyk πŸ’…

the UAS gets the re-INVITE (with offer2) before it ever gets an ACK for the ini-INVITE (with offer1) 😭. so what does Bob do? he sends a 200 OK (with answer2) to the re-INVITE (F8) 🫢 because he already sent a 200 OK (with answer1) to the ini-INVITE (F3, F5) and the dialog is already established ✨. (F5 is just a retransmission of F3, so no SDP negotiation happens here πŸ™ˆ)

as you'll see in Section 3.3.2 below, the 491 response is closely tied to session establishment, even when it's not an INVITE crossover 🧚. this example recommends sending 200 instead of 491, because the 200 doesn't affect the session πŸ’–. BUT a 491 response can totally lead to the same outcome, so either one can be used 🌈 literally your choice bestie.

also, if the UAS doesn't get an ACK for a long time, it should send a BYE and terminate the dialog πŸ‘‹. quick note: ACK F7 has the same CSeq number as ini-INVITE F1 (see Section 13.2.2.4 of RFC 3261 [1]) πŸ“. the UA should not reject or drop the ACK just because of the CSeq number, so don't be that person πŸ’€.

Note: implementation issues are outside the scope of this document πŸ™ˆ, but here's a tip for avoiding race conditions like this one. the caller can delay sending re-INVITE F6 for some period of time (2 seconds, perhaps) ⏳, and after that the caller can reasonably assume its ACK has been received πŸŽ€. implementors can decouple what the user does (like pressing the hold button 🌸) from what the protocol does (sending re-INVITE F6), so the UA can behave that way πŸ’…. in that case, it's up to the implementor how long to wait ✨. in most cases, an implementation like this may be useful for preventing the kind of race condition shown in this section πŸ¦‹. this document doesn't express a preference about whether or not they should wait for an ACK to be delivered πŸ€·β€β™€οΈ. after thinking about the impact on user experience, implementors should decide whether or not to wait a while πŸ’œ, because the user experience depends on the implementation and has no direct bearing on protocol behavior 🫠.

Message Details πŸŽ€

F1 INVITE Alice -> Bob 🍼

INVITE sip:bob@biloxi.example.com SIP/2.0
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
Max-Forwards: 70
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 INVITE
Contact: <sip:alice@client.atlanta.example.com;transport=udp>
Content-Type: application/sdp
Content-Length: 137

v=0
o=alice 2890844526 2890844526 IN IP4 client.atlanta.example.com
s=-
c=IN IP4 192.0.2.101
t=0 0
m=audio 49172 RTP/AVP 0
a=rtpmap:0 PCMU/8000
/* Detailed messages are shown for the sequence to illustrate the
   offer and answer examples.  */

F2 180 Ringing Bob -> Alice πŸ””

SIP/2.0 180 Ringing
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
;received=192.0.2.101
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>;tag=8321234356
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 INVITE
Contact: <sip:bob@client.biloxi.example.com;transport=udp>
Content-Length: 0

F3 200 OK Bob -> Alice βœ…

SIP/2.0 200 OK
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
;received=192.0.2.101
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>;tag=8321234356
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 INVITE
Contact: <sip:bob@client.biloxi.example.com;transport=udp>
Content-Type: application/sdp
Content-Length: 133

v=0
o=bob 2890844527 2890844527 IN IP4 client.biloxi.example.com
s=-
c=IN IP4 192.0.2.201
t=0 0
m=audio 3456 RTP/AVP 0
a=rtpmap:0 PCMU/8000

F4 ACK Alice -> Bob πŸ™ƒ

ACK sip:bob@client.biloxi.example.com SIP/2.0
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bKnashd8
Max-Forwards: 70
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>;tag=8321234356

Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 ACK
Content-Length: 0
/* The ACK request is lost.  */

F5(=F3) 200 OK Bob -> Alice (retransmission) πŸ”

/* The UAS retransmits a 200 OK to the ini-INVITE since it has not
   received an ACK.  */

F6 re-INVITE Alice -> Bob 🫣

INVITE sip:sip:bob@client.biloxi.example.com SIP/2.0
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bK74bf91
Max-Forwards: 70
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>;tag=8321234356
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 INVITE
Content-Length: 147

v=0
o=alice 2890844526 2890844527 IN IP4 client.atlanta.example.com
s=-
c=IN IP4 192.0.2.101
t=0 0
m=audio 49172 RTP/AVP 0
a=rtpmap:0 PCMU/8000
a=sendonly

F7(=F4) ACK Alice -> Bob (retransmission) πŸ”

/* "(=F4)" of ACK F7 shows that it is equivalent to F4 in that it is
   an ACK for F3.  This doesn't mean that F4 and F7 must be equal in
   Via-branch value.  Although it is ambiguous in RFC 3261 whether
   the Via-branch of ACK F7 differs from that of F4, it doesn't
   affect the UAS's behavior. */

F8 200 OK (re-INVITE) Bob -> Alice 🌸

SIP/2.0 200 OK
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bK74bf91
Max-Forwards: 70

From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>;tag=8321234356
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 INVITE
Content-Length: 143
v=0
o=bob 2890844527 2890844528 IN IP4 client.biloxi.example.com
s=-
c=IN IP4 192.0.2.201
t=0 0
m=audio 3456 RTP/AVP 0
a=rtpmap:0 PCMU/8000
a=recvonly

F9 ACK (re-INVITE) Alice -> Bob πŸŽ‰

ACK sip:sip:bob@client.biloxi.example.com SIP/2.0
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bK230f21
Max-Forwards: 70
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>;tag=8321234356
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 ACK
Content-Length: 0

3.1.5. Callee Receives re-INVITE (Established State) While in the Moratorium State (Case 2) ✨

okay bestie, this one is basically the same drama as 3.1.4 πŸŽ€ but with a twist: Bob sends an offer in the 200 and Alice sends an answer in the ACK. so now the offer in the 200 (F3) and the offer in the re-INVITE (F6) crash into each other πŸ’₯

State  Alice                          Bob  State
       |                                |
       |    ini-INVITE (no offer) F1    |
       |------------------------------->|
  Pre  |             180 F2             |  Pre
       |<-------------------------------|
  Ear  |                                |  Ear
       |    200(ini-INV) w/offer1 F3    |
       |<-------------------------------|
 Mora  |  ACK w/answer1 F4(packet loss) |  Mora
       |-------------------->x          |
  Est  |                                |
       | re-INVITE F6      200 F5(=F3)  |
       |   w/offer2         w/offer1    |
       |-------------     --------------|
       |              \ /               |
       |               X                |
       |              / \               |
       |<------------     ------------->|
       | ACK F7(=F4)      491(re-INV) F8|
       |-------------     --------------|
       |              \ /               |
       |               X                |
       |              / \               |
       |<------------     ------------->|
       |        ACK (re-INV) F9         |  Est
       |------------------------------->|
       |                                |
       |                                |

so Bob slams the brakes and sends a 491 to the re-INVITE (F6) πŸ™…β€β™€οΈ and that's because he can't handle a brand-new request until he gets an answer. (ngl this is the responsible move 🫢) (side quest: a 500 with a Retry-After header could also show up if you read the 491 as "request collision" πŸ€” but the doc says 491 is the recommended pick here, because 500 covers SO many situations that nobody could tell what actually went wrong πŸ’€) and the exact same outcome happens if F6 is an UPDATE with an offer ✨

Note: remember 3.1.4? the caller can wait on sending the re-INVITE F6 for a little bit (2 seconds, perhaps πŸ•‘). after that, the caller can reasonably assume its ACK got through, and that helps dodge this kind of race condition πŸ¦‹ the doc doesn't pick a side on whether anyone should wait for the ACK to actually land. implementors get to weigh the user experience and decide if they want to pause. the user experience depends on the implementation and doesn't really affect how the protocol behaves, so it's totally their call πŸ’…

Message Details

F1 INVITE Alice -> Bob

INVITE sip:bob@biloxi.example.com SIP/2.0
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
Max-Forwards: 70
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 INVITE
Contact: <sip:alice@client.atlanta.example.com;transport=udp>
Content-Length: 0

/* The request does not contain an offer.  Detailed messages are
   shown for the sequence to illustrate offer and answer
   examples.  */

F2 180 Ringing Bob -> Alice

F3 200 OK Bob -> Alice

SIP/2.0 200 OK
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
;received=192.0.2.101
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>;tag=8321234356
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 INVITE
Contact: <sip:bob@client.biloxi.example.com;transport=udp>
Content-Type: application/sdp
Content-Length: 133

v=0
o=bob 2890844527 2890844527 IN IP4 client.biloxi.example.com
s=-
c=IN IP4 192.0.2.201
t=0 0
m=audio 3456 RTP/AVP 0
a=rtpmap:0 PCMU/8000

/* An offer is made in 200.  */

F4 ACK Alice -> Bob

ACK sip:bob@client.biloxi.example.com SIP/2.0
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bKnashd8
Max-Forwards: 70
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>;tag=8321234356
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 ACK
Content-Type: application/sdp
Content-Length: 137

v=0
o=alice 2890844526 2890844526 IN IP4 client.atlanta.example.com
s=-
c=IN IP4 192.0.2.101
t=0 0
m=audio 49172 RTP/AVP 0
a=rtpmap:0 PCMU/8000

/* The request contains an answer, but the request is lost.  */

F5(=F3) 200 OK Bob -> Alice (retransmission)

/* The UAS retransmits a 200 OK to the ini-INVITE since it has not
   received an ACK.  */

F6 re-INVITE Alice -> Bob

INVITE sip:sip:bob@client.biloxi.example.com SIP/2.0
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bK74bf91
Max-Forwards: 70
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>;tag=8321234356
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 INVITE
Content-Length: 147

v=0
o=alice 2890844526 2890844527 IN IP4 client.atlanta.example.com
s=-
c=IN IP4 192.0.2.101
t=0 0
m=audio 49172 RTP/AVP 0
a=rtpmap:0 PCMU/8000
a=sendonly

/* The request contains an offer.  */

F7(=F4) ACK Alice -> Bob (retransmission)

/* A retransmission triggered by the reception of a retransmitted
   200. "(=F4)" of ACK F7 shows that it is equivalent to the F4 in
   that it is an ACK for F3.  This doesn't mean that F4 and F7 are
   necessarily equal in Via-branch value.  Although it is ambiguous
   in RFC 3261 whether the Via-branch of ACK F7 differs from that of
   F4, it doesn't affect the UAS's behavior.  */

F8 491 (re-INVITE) Bob -> Alice

/* Bob sends 491 (Request Pending), since Bob has a pending
   offer.  */

F9 ACK (re-INVITE) Alice -> Bob


3.1.6. Callee Receives BYE (Established State) While in the Moratorium State ☎️

this is the race where Bob, the UAS, gets an Established-state message, a BYE, while he's still stuck in the Moratorium state 😬 the ACK for the 200 OK goes missing (or shows up late), so Bob re-sends the 200 OK to the ini-INVITE. at the very same moment, Alice sends a BYE and ends the session πŸ“ž

here's the tea β˜• Alice's UA might see that retransmitted 200 OK and think "ooh, let's reestablish the session!" but no, that is wrong πŸ™…β€β™€οΈ the session should NOT get reestablished once the dialog is in the Mortal state. and if the UAS sent an offer in its 200 OK, the UAS also shouldn't start the session up again, for the same reason, if it gets a retransmitted ACK after it has already received a BYE πŸ’…

State  Alice                     Bob  State
       |                           |
       |         INVITE F1         |
       |-------------------------->|
  Pre  |      180 Ringing F2       |  Pre
       |<--------------------------|
  Ear  |                           |  Ear
       |         200 OK F3         |
       |<--------------------------|
 Mora  |    ACK F4(packet loss)    |  Mora
       |--------------->x          |
  Est  |   Both Way RTP Media      |
       |<=========================>|
       |   BYE F6       200 F5(=F3)|
       |-----------     -----------|
 Mort  |            \ /            |
       |             X             |
       |            / \            |
       |<----------     ---------->|  *race*
       |ACK F7(=F4)     200(BYE) F8|  Mort
       |-----------     -----------|
       |            \ /            |
       |             X             |
       |            / \            |
       |<----------     ---------->|
       | ^                       ^ |
       | | Timer K               | |
       | V                       | |
 Morg  |                 Timer J | |
       |                         V |
       |                           |  Morg
       |                           |

this scenario shows the race that happens when the UAS gets an Established-state BYE while it's in the Moratorium state. an ACK for a 200 OK goes missing (or gets delayed) 🐒 Bob re-sends the 200 OK to the ini-INVITE, and Alice fires off a BYE to end the session right then πŸ’”

Note: remember how 3.1.4 said implementation stuff is out of scope? same vibe here, but the doc still drops a tip for avoiding this type of race 🧚 the caller can hold off on sending BYE F6 for a little while (2 seconds, perhaps ⏳). after that, the caller can reasonably assume its ACK arrived. implementors can split the user's actions (like hanging up) from the protocol's actions (sending the BYE F6), so the UA can act this way 🌸 in that case, how long to wait is the implementor's call. most of the time, this kind of implementation can help prevent the race shown in this section ✨ the doc again doesn't take a side on waiting for the ACK to be delivered, and implementors should weigh the user experience before deciding whether to pause. the user experience depends on the implementation and has no direct bearing on protocol behavior 🫢

Message Details

F1 INVITE Alice -> Bob

F2 180 Ringing Bob -> Alice

F3 200 OK Bob -> Alice

F4 ACK Alice -> Bob

/* ACK request is lost.  */

F5(=F3) 200 OK Bob -> Alice

/* The UAS retransmits a 200 OK to the ini-INVITE since it has not
   received an ACK.  */

F6 BYE Alice -> Bob

/* Bob retransmits a 200 OK and Alice sends a BYE at the same time.
   Alice transitions to the Mortal state, so she does not begin a
   session after this even though she receives a 200 response to the
   re-INVITE.  */

F7(=F4) ACK Alice -> Bob

/* "(=F4)" of ACK F7 shows that it is equivalent to the F4 in that it
   is an ACK for F3.  This doesn't mean that F4 and F7 must be equal
   in Via-branch value.  Although it is ambiguous in RFC 3261 whether
   the Via-branch of ACK F7 differs from that of F4, it doesn't
   affect the UAS's behavior.  */

F8 200 OK (BYE) Bob -> Alice

/* Bob sends a 200 OK to the BYE.  */

3.2. Receiving Message in the Mortal State πŸ’€

this section is all about the call flow race conditions you can run into when messages from other states show up while you're in the Mortal state πŸ‘€ let's see what chaos looks like next 🌈

3.2.1. UA Receives BYE (Established State) While in the Mortal State ✨

State  Alice                  Bob  State
       |                        |
       |       INVITE F1        |
       |----------------------->|
  Pre  |    180 Ringing F2      |  Pre
       |<-----------------------|
  Ear  |                        |  Ear
       |       200 OK F3        |
       |<-----------------------|
 Mora  |         ACK F4         |  Mora
       |----------------------->|
  Est  |   Both Way RTP Media   |  Est
       |<======================>|
       |                        |
       | BYE F5         BYE F6  |
       |---------     ----------|
 Mort  |          \ /           |  Mort
       |           X            |
       |          / \           |
       |<--------     --------->|  *race*
       |                        |
       | 200 F8         200 F7  |
       |---------     ----------|
       |          \ /           |
       |           X            |
       |          / \           |
       |<--------     --------->|
       | ^                    ^ |
       | | Timer K            | |
       | V                    | |
 Morg  |              Timer J | |
       |                      V |
       |                        |  Morg
       |                        |

okay so this scenario is about the race condition that happens when the UAS gets an Established message, a BYE, while it's chilling in the Mortal state πŸ’– Alice and Bob both send a BYE at the exact same moment. A dialog and session end pretty much right after a BYE request gets passed to a client transaction. As Section 2 shows, the UA stays in the Mortal state. πŸ¦‹

UAs in the Mortal state send error responses to requests that operate within a dialog or session, like re-INVITE, UPDATE, or REFER. But the UA shall return a 200 OK to the BYE, considering the use case where a caller and a callee swap reports about the session as it's wrapping up πŸŽ€. Since the dialog and the session both end once a BYE is sent, choosing a 200 or an error response for a BYE received in the Mortal state doesn't change the final termination. So even though this example uses a 200, other responses can also work 🫢.

Message Details

F1 INVITE Alice -> Bob

F2 180 Ringing Bob -> Alice

F3 200 OK Bob -> Alice

F4 ACK Alice -> Bob

F5 BYE Alice -> Bob

Okay, so the session is over the moment Alice sends this BYE πŸ’…. The dialog still exists at that point, but it's definitely going to be terminated in a short while. It's fully gone once the timeout of the BYE request happens. 🌸

F6 BYE Bob -> Alice

Bob sent a BYE at the same time as Alice πŸ™ˆ. He ends the session and the dialog.

F7 200 OK Bob -> Alice

Since the dialog is in the Moratorium state, Bob replies with a 200 to the BYE request. ✨

F8 200 OK Alice -> Bob

Alice went from the Established state to the Mortal state when she sent her BYE, so she replies with a 200 to the BYE request. πŸ‘‘


3.2.2. UA Receives re-INVITE (Established State) While in the Mortal State 🧚

 State  Alice                  Bob  State
        |                        |
        |       INVITE F1        |
        |----------------------->|
   Pre  |    180 Ringing F2      |  Pre
        |<-----------------------|
   Ear  |                        |  Ear
        |       200 OK F3        |
        |<-----------------------|
  Mora  |         ACK F4         |  Mora
        |----------------------->|
   Est  |   Both Way RTP Media   |  Est
        |<======================>|
        |                        |
        | BYE F5     re-INVITE F6|
        |---------     ----------|
  Mort  |          \ /           |
        |           X            |
        |          / \           |
*race*  |<--------     --------->|
        |                        |  Mort
        | 481 F8         200 F7  |
        | (re-INV)       (BYE)   |
        |---------     ----------|
        |          \ /           |^
        |           X            ||
        |          / \           ||Timer J
        |<--------     --------->||
       ^|    ACK (re-INV) F9     ||
       ||<-----------------------||
Timer K||                        ||
       V|                        ||
  Morg  |                        |V
        |                        |  Morg
        |                        |

This one shows the race that happens when the UAS gets an Established message, a re-INVITE, while it's in the Mortal state 😭. Bob sends a re-INVITE, and Alice sends a BYE at the same time. The re-INVITE gets a 481 response, because Alice's TU already went from the Established state to the Mortal state by sending the BYE. Bob sends an ACK for the 481 because the ACK for error responses is handled by the transaction layer. When the 481 shows up, the INVITE client transaction still hangs around, even though the dialog has been terminated. πŸ’€

Message Details

F1 INVITE Alice -> Bob

F2 180 Ringing Bob -> Alice

F3 200 OK Bob -> Alice

F4 ACK Alice -> Bob

F5 BYE Alice -> Bob

Alice sends a BYE, which ends the session, and she moves from the Established state to the Mortal state. πŸ“

F6 re-INVITE Bob -> Alice

Alice sends her BYE while Bob sends a re-INVITE at the same time 😀. The dialog state flips to the Mortal state the moment Alice sends the BYE, but Bob doesn't know that until he gets the BYE. So from Alice's point of view the dialog is Terminated, but from Bob's point of view it's still Confirmed. That's the race condition, babe. πŸ”₯

F7 200 OK (BYE) Bob -> Alice

F8 481 Call/Transaction Does Not Exist (re-INVITE) Alice -> Bob

Since Alice is in the Mortal state, she responds to the re-INVITE with a 481. πŸ™…β€β™€οΈ

F9 ACK (re-INVITE) Bob -> Alice

The ACK for this error response is handled by Bob's INVITE client transaction. πŸŽ€


3.2.3. UA Receives 200 OK for re-INVITE (Established State) While in the Mortal State 🌈

State  Alice                  Bob  State
       |                        |
       |       INVITE F1        |
       |----------------------->|
  Pre  |    180 Ringing F2      |  Pre
       |<-----------------------|
  Ear  |                        |  Ear
       |       200 OK F3        |
       |<-----------------------|
 Mora  |         ACK F4         |  Mora
       |----------------------->|
  Est  |   Both Way RTP Media   |  Est
       |<======================>|
       |                        |
       |      re-INVITE F5      |
       |<-----------------------|
       | 200 F7         BYE F6  |
       |---------     ----------|
       |          \ /           |  Mort
       |           X            |
       |          / \           |
       |<--------     --------->|  *race*
 Mort  | 200 F8         ACK F9  |
       | (BYE)         (re-INV) |
       |---------     ----------|
       | ^        \ /           |
       | |         X            |
       | |        / \           |
       |<--------     --------->|
       | |                    ^ |
       | |            Timer K | |
       | |                    V |
       | | Timer J              |  Morg
       | V                      |
 Morg  |                        |
       |                        |

This scenario is the race condition that happens when the UAS gets an Established message, a 200 to a re-INVITE, while it's in the Mortal state 🫠. Bob sends a BYE right after sending a re-INVITE. (Think of a phone app: someone hangs up right after refreshing the session πŸ“±.) Bob sends an ACK for the 200 response to the INVITE while he's in the Mortal state, which completes the INVITE transaction. ✨

Note: As noted in Section 3.1.4, implementation issues are outside the scope of this document, but the following tip is provided for avoiding race conditions of this type. The UAC can delay sending a BYE F6 until the re-INVITE transaction F5 completes. Implementors can decouple the actions of the user (e.g., hanging up) from the actions of the protocol (the sending of BYE F6), so that the UA can behave like this. In this case, it is the implementor's choice as to how long to wait. In most cases, such an implementation may be useful in preventing the type of race condition described in this section. This document expresses no preference about whether or not they should wait for an ACK to be delivered. After considering the impact on user experience, implementors should decide whether or not to wait for a while, because the user experience depends on the implementation and has no direct bearing on protocol behavior.

Message Details

F1 INVITE Alice -> Bob

F2 180 Ringing Bob -> Alice

F3 200 OK Bob -> Alice

F4 ACK Alice -> Bob

F5 re-INVITE Bob -> Alice

INVITE sip:alice@client.atlanta.example.com SIP/2.0
Via: SIP/2.0/UDP client.biloxi.example.com:5060;branch=z9hG4bKnashd7
Session-Expires: 300;refresher=uac
Supported: timer
Max-Forwards: 70
From: Bob <sip:bob@biloxi.example.com>;tag=8321234356
To: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 INVITE
Content-Length: 0

Some detailed messages are shown here so you can see that the re-INVITE is handled in the usual way in the Mortal state 🧐.

F6 BYE Bob -> Alice

Bob sends the BYE right after the re-INVITE. He ends the session and moves from the Established state to the Mortal state πŸ₯².

F7 200 OK (re-INVITE) Alice -> Bob

SIP/2.0 200 OK
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bKnashd7
;received=192.0.2.201
Require: timer
Session-Expires: 300;refresher=uac
From: Bob <sip:bob@biloxi.example.com>;tag=8321234356
To: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 INVITE
Content-Length: 0

Bob sent the BYE, and Alice answers the re-INVITE with a 200 OK. A race condition happens here πŸƒβ€β™€οΈπŸ’¨.

F8 200 OK (BYE) Alice -> Bob

F9 ACK (re-INVITE) Bob -> Alice

ACK sip:alice@client.atlanta.example.com SIP/2.0
Via: SIP/2.0/UDP client.biloxi.example.com:5060;branch=z9hG4bK74b44
Max-Forwards: 70
From: Bob <sip:bob@biloxi.example.com>;tag=8321234356
To: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 ACK
Content-Length: 0

Bob sends this ACK while he's in the Mortal state, which completes the three-way handshake of the INVITE transaction. πŸ€πŸ’œ

3.2.4. Callee Receives ACK (Moratorium State) While in the Mortal State ✨

State  Alice                          Bob  State
       |                                |
       |         ini-INVITE F1          |
       |------------------------------->|
  Pre  |            180 F2              |  Pre
       |<-------------------------------|
  Ear  |            200 F3              |  Ear
       |<-------------------------------|
 Mora  |                                |  Mora
       |    ACK F4            BYE F5    |
       |-------------     --------------|
  Est  |              \ /               |  Mort
       |               X                |
       |              / \               |
       |<------------     ------------->|  *race*
 Mort  |            200 F6              |
       |------------------------------->|
       | ^                            ^ |
       | |                    Timer K | |
       | |                            V |
       | | Timer J                      |  Morg
       | V                              |
 Morg  |                                |
       |                                |

Okay so this scenario shows the race condition that happens when the UAS gets an Established message, the ACK to the 200, while it's sitting in the Mortal state πŸ’… Alice sends an ACK and Bob sends a BYE at the exact same time. When the offer is in a 2xx and the answer is in an ACK, there's a race condition 😭 A session doesn't get started when the ACK arrives, because Bob already ended it by sending a BYE. The answer in the ACK request just gets ignored πŸ’€

Note: As noted in Section 3.1.4, implementation issues are outside the scope of this document, but the following tip is provided for avoiding race conditions of this type. Implementors can decouple the actions of the user (e.g., hanging up) from the actions of the protocol (the sending of BYE F5), so that the UA can behave like this. In this case, it is the implementor's choice as to how long to wait. In most cases, such an implementation may be useful in preventing the type of race condition described in this section. This document expresses no preference about whether or not they should wait for an ACK to be delivered. After considering the impact on user experience, implementors should decide whether or not to wait for a while, because the user experience depends on the implementation and has no direct bearing on protocol behavior.

Message Details

F1 INVITE Alice -> Bob

F2 180 Ringing Bob -> Alice

F3 200 OK Bob -> Alice

F4 ACK Alice -> Bob

/* RTP streams are established between Alice and Bob.  */

F5 BYE Alice -> Bob

F6 200 OK Bob -> Alice

/* Alice sends a BYE and terminates the session and dialog.  */

3.3. Other Race Conditions 🌸

This section shows examples of race conditions that aren't directly related to dialog state transitions 🧚 In SIP, processing functions are deployed in three layers: dialog, session, and transaction. They're related to each other, but they have to be treated separately. Section 17 of RFC 3261 [1] details the processing of transactions. This document has tried so far to clarify the processing on dialogs. This section explains race conditions that are related to sessions established with SIP ✨

3.3.1. Re-INVITE Crossover πŸ¦‹

Alice                         Bob
  |                            |
  |         INVITE F1          |
  |--------------------------->|
  |      180 Ringing F2        |
  |<---------------------------|
  |          200 OK F3         |
  |<---------------------------|
  |           ACK F4           |
  |--------------------------->|
  |     Both Way RTP Media     |
  |<==========================>|
  |                            |
  |re-INVITE F5   re-INVITE F6 |
  |------------   -------------|
  |            \ /             |
  |             X              |
  |            / \             |
  |<-----------   ------------>|
  |   491 F8        491 F7     |
  |------------   -------------|
  |            \ /             |
  |             X              |
  |            / \             |
  |<-----------   ------------>|
  |  ^ ACK F9         ^ ACK F10|
  |--|---------   ----|--------|
  |  |          \ /   |        |
  |  |           X    |        |
  |  |          / \   |        |
  |<-|----------   ---|------->|
  |  |                |        |
  |  |0-2.0 sec       |        |
  |  |                |        |
  |  v  re-INVITE F11(=F6)     |
  |<------------------|--------|
  |     200 OK F12    |        |
  |-------------------|------->|
  |       ACK F13     |        |
  |<------------------|--------|
  |                   |        |
  |                   |2.1-4.0 sec
  |                   |        |
  |re-INVITE F14(=F5) v        |
  |--------------------------->|
  |         200 OK F15         |
  |<---------------------------|
  |          ACK F16           |
  |--------------------------->|
  |                            |
  |                            |

Okay this one is so good 😭 In this scenario, Alice and Bob send re-INVITEs at the same time. When two re-INVITEs cross in the same dialog, they get retried, each after a different interval, according to Section 14.1 of RFC 3261 [1]. When Alice sends her re-INVITE and it crosses with Bob's, her re-INVITE gets retried after 2.1-4.0 seconds, because she owns the Call-ID (she generated it) πŸ‘‘ Bob retries his INVITE after 0.0-2.0 seconds, because Bob isn't the owner of the Call-ID.

Therefore, each User Agent must remember whether or not it has generated the Call-ID of the dialog, in case an INVITE may cross with another INVITE 🫢

In this example, Alice's re-INVITE is for session modification and Bob's re-INVITE is for session refresh. In this case, after the 491 responses, Bob retries the re-INVITE for session refresh earlier than Alice does. If Alice was to retry her re-INVITE (that is, if she was not the owner of Call-ID), the request would refresh and modify the session at the same time. Then Bob would know that he does not need to retry his re-INVITE to refresh the session 🌈

In another instance, where two re-INVITEs for session modification cross over, retrying the same re-INVITE again after a 491 by the Call-ID owner (the UA that retries its re-INVITE after the other UA) may result in unintended behavior, so the UA must decide if the retry of the re-INVITE is necessary. (For example, when a call hold and an addition of video media cross over, a mere retry of the re-INVITE at the firing of the timer may result in the situation where the video is transmitted immediately after the holding of the audio. This behavior is probably not intended by the users.) πŸ™ˆ

Message Details

F1 INVITE Alice -> Bob

F2 180 Ringing Bob -> Alice

F3 200 OK Bob -> Alice

F4 ACK Alice -> Bob

F5 re-INVITE Alice -> Bob
INVITE sip:sip:bob@client.biloxi.example.com SIP/2.0
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
Max-Forwards: 70
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>;tag=8321234356
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 INVITE
Content-Length: 147

v=0
o=alice 2890844526 2890844527 IN IP4 client.atlanta.example.com
s=-
c=IN IP4 192.0.2.101
t=0 0
m=audio 49172 RTP/AVP 0
a=rtpmap:0 PCMU/8000
a=sendonly
/* Some detailed messages are shown for the sequence to illustrate
   what sort of INVITE requests crossed over each other.  */
F6 re-INVITE Bob -> Alice
INVITE sip:alice@client.atlanta.example.com SIP/2.0
Via: SIP/2.0/UDP client.biloxi.example.com:5060;branch=z9hG4bKnashd7
Session-Expires: 300;refresher=uac
Supported: timer
Max-Forwards: 70
From: Bob <sip:bob@biloxi.example.com>;tag=8321234356
To: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 INVITE
Content-Length: 0
/* A re-INVITE request for a session refresh and another for a call
   hold are sent at the same time.  */
F7 491 Request Pending Bob -> Alice

/* Since a re-INVITE is in progress, a 491 response is returned.  */

F8 491 Request Pending Alice -> Bob

F9 ACK (INVITE) Alice -> Bob

F10 ACK (INVITE) Bob -> Alice
F11 re-INVITE Bob -> Alice
INVITE sip:alice@client.atlanta.example.com SIP/2.0
Via: SIP/2.0/UDP client.biloxi.example.com:5060;branch=z9hG4bKnashd71

Session-Expires: 300;refresher=uac
Supported: timer
Max-Forwards: 70
From: Bob <sip:bob@biloxi.example.com>;tag=8321234356
To: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 INVITE
Content-Type: application/sdp
Content-Length: 133

v=0
o=bob 2890844527 2890844527 IN IP4 client.biloxi.example.com
s=-
c=IN IP4 192.0.2.201
t=0 0
m=audio 3456 RTP/AVP 0
a=rtpmap:0 PCMU/8000
/* Since Bob is not the owner of the Call-ID, he sends a re-INVITE
   again after 0.0-2.0 seconds.  */
F12 200 OK Alice -> Bob

F13 ACK Bob -> Alice

F14 re-INVITE Alice -> Bob
INVITE sip:sip:bob@client.biloxi.example.com SIP/2.0
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bK74bf91
Max-Forwards: 70
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>;tag=8321234356
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 3 INVITE
Content-Length: 147

v=0
o=alice 2890844526 2890844527 IN IP4 client.atlanta.example.com
s=-
c=IN IP4 192.0.2.101
t=0 0
m=audio 49172 RTP/AVP 0
a=rtpmap:0 PCMU/8000
a=sendonly
/* Since Alice is the owner of the Call-ID, Alice sends a re-INVITE
   again after 2.1-4.0 seconds.  */
F15 200 OK Bob -> Alice

F16 ACK Alice -> Bob

3.3.2. UPDATE and re-INVITE Crossover ✨

Okay so this one is a whole drama 🍿 Alice and Bob try to change the session at the exact same moment, so their requests cross in the middle. Here's the full call flow πŸ¦‹

   Alice                         Bob
     |                            |
     |         INVITE F1          |
     |--------------------------->|
     |      180 Ringing F2        |
     |<---------------------------|
     |                            |
     |          200 OK F3         |
     |<---------------------------|
     |           ACK F4           |
     |--------------------------->|
     |     Both Way RTP Media     |
     |<==========================>|
     |                            |
     |  UPDATE F5    re-INVITE F6 |
     |------------   -------------|
     |            \ /             |
     |             X              |
     |            / \             |
     |<-----------   ------------>|
     |   491 F8        491 F7     |
     |   (re-INVITE)   (UPDATE)   |
     |------------   -------------|
     |            \ /             |
     |             X              |
     |            / \             |
     |<-----------   ------------>|
     |  ^       ACK F9   ^        |
     |<-|----------------|--------|
     |  |                |        |
     |  |0-2.0 sec       |        |
     |  |                |        |
     |  v  re-INVITE F10 |        |
     |<------------------|--------|
     |     200 OK F11    |        |
     |-------------------|------->|
     |       ACK F12     |        |
     |<------------------|--------|
     |                   |        |
     |                   |2.1-4.0 sec
     |                   |        |
     |      UPDATE F13   v        |
     |--------------------------->|
     |         200 OK F14         |
     |<---------------------------|
     |                            |
     |                            |

Here the UPDATE carries an SDP offer πŸ“¨, so the UPDATE and the re-INVITE both get a 491, just like in the regular re-INVITE crossover πŸ’…. When an UPDATE for a session refresh with no session description crosses a re-INVITE, both requests succeed with 200 ✨ (491 just means a UA already has a request pending, so it's basically "wait your turn" πŸ™ˆ). UPDATE crossover is the same story. In the former case, where either UPDATE carries a session description, the requests fail with 491; in the latter cases, they succeed with 200 πŸŽ€.

Quick note: a 491 gets sent because an SDP offer is still pending πŸ“, and 491 is an error tied to stuff that affects the session SIP set up 🫢.

Message Details

F1 INVITE Alice -> Bob

F2 180 Ringing Bob -> Alice

F3 200 OK Bob -> Alice

F4 ACK Alice -> Bob

F5 UPDATE Alice -> Bob

UPDATE sip:sip:bob@client.biloxi.example.com SIP/2.0
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
Max-Forwards: 70
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>;tag=8321234356
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 UPDATE
Content-Length: 147

v=0
o=alice 2890844526 2890844527 IN IP4 client.atlanta.example.com
s=-
c=IN IP4 192.0.2.101
t=0 0
m=audio 49172 RTP/AVP 0
a=rtpmap:0 PCMU/8000
a=sendonly

/* Some detailed messages are shown for the sequence to illustrate
   messages crossing over each other.  */

F6 re-INVITE Bob -> Alice

INVITE sip:alice@client.atlanta.example.com SIP/2.0
Via: SIP/2.0/UDP client.biloxi.example.com:5060;branch=z9hG4bKnashd7
Session-Expires: 300;refresher=uac
Supported: timer
Max-Forwards: 70
From: Bob <sip:bob@biloxi.example.com>;tag=8321234356
To: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 INVITE
Content-Type: application/sdp
Content-Length: 133

v=0
o=bob 2890844527 2890844527 IN IP4 client.biloxi.example.com
s=-
c=IN IP4 192.0.2.201
t=0 0
m=audio 3456 RTP/AVP 0
a=rtpmap:0 PCMU/8000

/* This is a case where a re-INVITE for a session refresh and an
   UPDATE for a call hold are sent at the same time.  */

F7 491 Request Pending (UPDATE) Bob -> Alice

/* Since a re-INVITE is in process, a 491 response is returned.  */

F8 491 Request Pending (re-INVITE) Alice -> Bob

F9 ACK (re-INVITE) Alice -> Bob

F10 re-INVITE Bob -> Alice

INVITE sip:alice@client.atlanta.example.com SIP/2.0
Via: SIP/2.0/UDP client.biloxi.example.com:5060;branch=z9hG4bKnashd71
Session-Expires: 300;refresher=uac
Supported: timer
Max-Forwards: 70

From: Bob <sip:bob@biloxi.example.com>;tag=8321234356
To: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 INVITE
Content-Type: application/sdp
Content-Length: 133

v=0
o=bob 2890844527 2890844527 IN IP4 client.biloxi.example.com
s=-
c=IN IP4 192.0.2.201
t=0 0
m=audio 3456 RTP/AVP 0
a=rtpmap:0 PCMU/8000

/* Since Bob is not the owner of the Call-ID, Bob sends an INVITE
   again after 0.0-2.0 seconds.  */

F11 200 OK Alice -> Bob

F12 ACK Bob -> Alice

F13 UPDATE Alice -> Bob

UPDATE sip:sip:bob@client.biloxi.example.com SIP/2.0
Via: SIP/2.0/UDP client.atlanta.example.com:5060;branch=z9hG4bK74bf91
Max-Forwards: 70
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>;tag=8321234356
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 3 UPDATE
Content-Length: 147

v=0
o=alice 2890844526 2890844527 IN IP4 client.atlanta.example.com
s=-
c=IN IP4 192.0.2.101
t=0 0
m=audio 49172 RTP/AVP 0
a=rtpmap:0 PCMU/8000
a=sendonly
/* Since Alice is the owner of the Call-ID, Alice sends the UPDATE
   again after 2.1-4.0 seconds.  */

F14 200 OK Bob -> Alice


3.3.3. Receiving REFER (Established State) While in the Mortal State 🎭

This is a race condition, and it's so dramatic 😭 Bob sends a REFER and Alice sends a BYE at the exact same time. Here's the play-by-play 🧚

  State  Alice                  Bob  State
         |                        |
         |       INVITE F1        |
         |----------------------->|
    Pre  |    180 Ringing F2      |  Pre
         |<-----------------------|
    Ear  |                        |  Ear
         |       200 OK F3       |
         |<-----------------------|
   Mora  |         ACK F4         |  Mora
         |----------------------->|
    Est  |   Both Way RTP Media   |  Est
         |<======================>|
         |                        |
         | BYE F5        REFER F6 |
         |---------     ----------|
   Mort  |          \ /           |
         |           X            |
         |          / \           |
 *race*  |<--------     --------->|
         |                        |  Mort
         | 481 F8         200 F7  |
         | (REFER)        (BYE)   |
         |---------     ----------|
         |          \ /         ^ |
         |           X          | |
         |          / \         | |
         |<--------     --------->|
         | ^                    | |
         | | Timer K            | |
         | V            Timer J | |
   Morg  |                      V |
         |                        |  Morg
         |                        |

So this scenario is about what happens when the UAS gets an Established message, a REFER, while it's already in the Mortal state πŸ‘€ Bob sends the REFER within the same dialog, while Alice sends her BYE. Alice's dialog moves into the Mortal state the moment she sends that BYE πŸ’€ In the Mortal state, the UA still has dialog info for an internal process, but the dialog shouldn't exist outwardly πŸ™ˆ So the UA sends an error response to the REFER, and that REFER came in as a mid-dialog request. Basically, Alice in the Mortal state sends an error response to the REFER πŸ’…

Here's the twist 🌈 Bob has already started the SUBSCRIBE usage with the REFER, so the dialog keeps going until that SUBSCRIBE usage ends, even though the INVITE dialog usage ends once BYE is received. Bob's behavior in this case needs to follow the procedures in RFC 5057 [6] πŸ“š

Message Details

F1 INVITE Alice -> Bob

F2 180 Ringing Bob -> Alice

F3 200 OK Bob -> Alice

F4 ACK Alice -> Bob

F5 BYE Alice -> Bob

/* Alice sends a BYE request and terminates the session, and
   transitions from the Confirmed state to the Terminated state.  */

F6 REFER Bob -> Alice

/* Alice sends a BYE, and Bob sends a REFER at the same time.  Bob
   sends the REFER on the INVITE dialog.  The dialog state
   transitions to the Mortal state at the moment Alice sends the BYE,
   but Bob doesn't know this until he receives the BYE.  A race
   condition occurs.  */

F7 200 OK (BYE) Bob -> Alice

F8 481 Call/Transaction Does Not Exist (REFER) Alice -> Bob

/* Alice in the Mortal state sends a 481 to the REFER.  */

4. Security Considerations πŸ’€

Okay, this part is serious, bestie 🫢 This document clarifies behavior that's already specified in RFC 3261 [1], RFC 3264 [2], and RFC 3515 [4]. Those documents' security considerations still apply after these clarifications, so don't skip them πŸ”’

5. Acknowledgements πŸ’œ

Big thank-you to everyone who gave comments on this document πŸŽ€ That's Robert Sparks, Dean Willis, Cullen Jennings, James M. Polk, Gonzalo Camarillo, Kenichi Ogami, Akihiro Shimizu, Mayumi Munakata, Yasunori Inagaki, Tadaatsu Kidokoro, Kenichi Hiragi, Dale Worley, Vijay K. Gurbani, and Anders Kristensen ✨

6. References πŸ“š

6.1. Normative References

[1] Rosenberg, J., Schulzrinne, H., Camarillo, G., Johnston, A., Peterson, J., Sparks, R., Handley, M., and E. Schooler, "SIP: Session Initiation Protocol", RFC 3261, June 2002.

[2] Rosenberg, J. and H. Schulzrinne, "An Offer/Answer Model with Session Description Protocol (SDP)", RFC 3264, June 2002.

[3] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997.

[4] Sparks, R., "The Session Initiation Protocol (SIP) Refer Method", RFC 3515, April 2003.

[5] Rosenberg, J. and H. Schulzrinne, "Reliability of Provisional Responses in Session Initiation Protocol (SIP)", RFC 3262, June 2002.

6.2. Informative References

[6] Sparks, R., "Multiple Dialog Usages in the Session Initiation Protocol", RFC 5057, November 2007.

[7] Sparks, R., "Correct transaction handling for 200 responses to Session Initiation Protocol INVITE requests", Work in Progress, July 2008.

Appendix A. BYE in the Early Dialog πŸ’…

okay so this bit is connected to Section 3.1.3 and it explains why BYE is not recommended in the Early state πŸ™ˆ we're gonna look at a case where a BYE sent during the early dialog causes total chaos bestie 😭

   Alice            Proxy               Bob   Carol
     |                |                  |      |
     |   INVITE F1    |                  |      |
     |--------------->|    INVITE F2     |      |
     |     100 F3     |----------------->|      |
     |<---------------| 180(To tag=A) F4 |      |
     |    180(A) F5   |<-----------------|      |
     |<---------------|                  |      |
     |                |       INVITE(Fork) F6   |
     |                |------------------------>|
     |                |                100 F7   |
     |    BYE(A) F8   |<------------------------|
     |--------------->|    BYE(A) F9     |      |
     |                |----------------->|      |
     |                |  200(A,BYE) F10  |      |
     | 200(A,BYE) F11 |<-----------------|      |
     |<---------------|  487(A,INV) F12  |      |
     |                |<-----------------|      |
     |                |    ACK(A) F13    |      |
     |                |----------------->|      |
     |                |                  |      |
     |                |                         |
     |                |     200(To tag=B) F13   |
     |   200(B) F14   |<------------------------|
     |<---------------|                         |
     |   ACK(B) F15   |                         |
     |--------------->|            ACK(B) F16   |
     |                |------------------------>|
     |   BYE(B) F17   |                         |
     |--------------->|            BYE(B) F18   |
     |                |------------------------>|
     |                |            200(B) F19   |
     |   200(B) F20   |<------------------------|
     |<---------------|                         |
     |                |                         |
     |                |                         |

careful sending a BYE in the Early state when you know a proxy is gonna fork the call πŸ‘€ in this example the BYE request goes through totally normally and successfully terminates the dialog with Bob ✨ after Bob ends the dialog by getting the BYE, he sends a 487 to the ini-INVITE. according to Section 15.1.2 of RFC 3261 [1], it is RECOMMENDED for the UAS to generate a 487 to any pending requests after receiving a BYE. so Bob sends that 487 to the ini-INVITE because he got the BYE while the ini-INVITE was still pending 🫢

but here's the tea 🍡 Alice still gets a final response to the INVITE (a 200 from Carol) even though she already successfully ended the dialog with Bob 😭 so no matter how the BYE in the Early state turns out, Alice MUST be prepared for a brand new dialog to get established until she receives the final response for the INVITE and terminates the INVITE transaction πŸ’€

it's not illegal to send a BYE in the Early state to end one specific early dialog πŸ¦‹ it might even satisfy what some callers want. BUT you gotta choose between BYE and CANCEL in the Early state very carefully. CANCEL is the move when you wanna abandon the whole call attempt entirely. BYE is the move when you only wanna abandon one particular early dialog while the call can still be completed with other destinations. no matter which one you use, the UAC must be prepared for the possibility that a call may still get established to one or more destinations 🌸

Appendix B. BYE Request Overlapping with re-INVITE 🧚

okay so this one's giving "I'm already in the call, why is everything happening at once" 😭 here's the setup:

     UAC                    UAS
      |                      |
   The session has been already established
     ==========================
      |   re-INVITE F1       |
      |--------------------->|
      |   BYE F2             |
      |--------------------->|
      |   200(BYE) F3        |
      |<---------------------|
      |   INVITE F4(=F1)     |
      |--------------------->|
      |                      |
      |                      |

this case could look like the one in Section 3.2.3, BUT it is not a race condition πŸ™…β€β™€οΈ this case shows what happens when there's no response to the INVITE for some reason. this appendix explains the behavior and the reasoning behind it, because this stuff is super likely to confuse people 🌈

first of all, it's SO important not to mix up the transaction layer with the dialog layer πŸ’… RFC 3261 [1] details the transaction layer behavior, and this document is where the dialog layer behavior lives. these two layers are independent of each other, even though both can change their states because of the same SIP messages. (a dialog can end even though a transaction is still hanging around, and the other way around too ✨)

in the sequence above, there's no response to F1, and F2 (BYE) gets sent right after F1. (F1 is a mid-dialog request. if F1 had been an ini-INVITE, the BYE couldn't be sent until the UAC received a provisional response to the request with a To tag πŸ™ˆ)

here's a figure showing the UAC's dialog state and the transaction state together:

   BYE   INV  dialog UAC                    UAS
                :     |                      |
                :     |                      |
                |     |   re-INVITE F1       |
          o     |     |--------------------->|
          |     |     |   BYE F2             |
    o     |  (Mortal) |--------------------->|
    |     |     |     |   200(BYE) F3        |
    |     |     |     |<---------------------|
    |     |     |     |   INVITE F4(=F1)     |
    |     |     |     |--------------------->|
    |     |     |     |   481(INV) F5        |
    |     |     |     |<---------------------|
    |     |     |     |   ACK(INV) F6        |
    |     |     |     |--------------------->|
    |     |     |     |                      |
    o     |     o     |                      |
          |           |                      |
          o           |                      |
                      |                      |

for the UAC, the INVITE client transaction begins at the point F1 is sent πŸ¦‹ the UAC sends BYE (F2) right after F1. this is a legit move. (usually each SIP method is independent of the others, BYE included. BUT it should be noted that it is prohibited to send a request with an SDP offer while the previous offer is still in progress πŸ‘€)

after that, F2 triggers the BYE client transaction. at the same time the dialog state moves to the Mortal state, and then only a BYE or a response to a BYE can be handled πŸ₯Ί

it IS permitted to send F4 (a retransmission of INVITE) in the Mortal state, because the retransmission of F1 is handled by the transaction layer, and the INVITE transaction hasn't moved to the Terminated state yet πŸ’œ like we said, the dialog and the transaction act independently of each other. so the transaction handling has to keep going even after the dialog has moved to the Terminated state ✨

Note: As noted in Section 3.1.4, implementation issues are outside the scope of this document, but the following tip is provided for avoiding race conditions of this type. The UAC can delay sending BYE F2 until the re-INVITE transaction F1 completes. Implementors can decouple the actions of the user (e.g., hanging up) from the actions of the protocol (the sending of BYE F2), so that the UA can behave like this. In this case, it is the implementor's choice as to how long to wait. In most cases, such an implementation may be useful to prevent this case. This document expresses no preference about whether or not they should wait for an ACK to be delivered. After considering the impact on user experience, implementors should decide whether or not to wait for a while, because the user experience depends on the implementation and has no direct bearing on protocol behavior.

next up, the UAS's state 🧐

   UAC                    UAS dialog  INV   BYE
    |                      |     :
    |                      |     :
    |   re-INVITE F1       |     |
    |-------------->x      |     |
    |   BYE F2             |     |
    |--------------------->|     |           o
    |   200(BYE) F3        |  (Mortal)       |
    |<---------------------|     |           |<-Start Timer J
    |   INVITE F4(=F1)     |     |           |
    |--------------------->|     |     o     |
    |   4xx/5xx(INV) F5    |     o     |     o
    |<---------------------|           |
    |   ACK(INV) F6        |           |
    |--------------------->|           |<-Start Timer I
    |                      |           |
    |                      |           |
    |                      |           o
    |                      |

for the UAS, it can be considered that packet F1 got lost or delayed (here the behavior is explained for the case where the UAS receives F2 BYE before F1 INVITE πŸ™ƒ). so F2 triggers the BYE transaction for the UAS, and at the same time the dialog moves to the Mortal state. then, when F4 shows up, the INVITE server transaction begins. (it is permitted to start the INVITE server transaction in the Mortal state πŸ’… the INVITE server transaction begins to handle the received SIP request regardless of the dialog state.) the UAS's TU sends an appropriate error response for the F4 INVITE, either 481 (because the TU knows that the dialog matching the INVITE is in the Terminated state) or 500 (because the re-sent F4 has an out-of-order CSeq). (it was mentioned above that INVITE message F4 (and F1) is a mid-dialog request, and mid-dialog requests have a To tag. it should be noted that the UAS's TU does not begin a new dialog upon receiving an INVITE with a To tag πŸŽ€)

Appendix C. UA's Behavior for CANCEL ✨

okay so this section is about the CANCEL behaviors that sneakily affect the dialog state transition in the Early state πŸ‘€ CANCEL doesn't change anything on the UAC's dialog state πŸ™…β€β™€οΈ but it still has an indirect influence, because it messes with the ini-INVITE in a big way πŸ’… For the UAS, CANCEL has way more direct effects on the dialog than it does when the UAC sends one, since it can be the trigger for sending the 487 response πŸ”₯ Figure 3 walks through the UAS's behavior in the Early state πŸ¦‹ This flow diagram is just an explanatory figure, and the real dialog state transitions are the ones shown in Figures 1 and 2 🌸

In the flow, solid lines are about dialog state transitions, and dotted lines are about CANCEL πŸ’œ (r) means reception of signaling and (s) means sending πŸ“‘ There's no dialog state for CANCEL, but the Cancelled state is handled virtually here, just to make it easier to understand how the UA behaves when it sends and receives CANCEL 🧚

                  +-------------+
                  | Preparative |---+
                  +-------------+   |
                    :   | 1xx(s)    |
                    :   V           |
                    : +-------+     | 2xx(s)
                    : | Early |-----+------+
                    : +-------+            |
                    :     :                V
                    :     :           +-----------+
                    :     :           | Confirmed |<...
                    :.....:           +-----------+   :
                       :                   |  :       :
                       :             BYE(r)|  :       :
                       : CANCEL(r)         |  :.......:
                       V                   |    CANCEL(r)
                   .............           |
                   : Cancelled :           |
                   :...........:           |
                      | 487(s)             |
                      |                    |
                      +--------------------+
                                 |
                                 V
                           +------------+
                           | Terminated |
                           +------------+

                   Figure 3: CANCEL flow diagram for UAS

There are two behaviors for the UAS, and which one kicks in depends on the state it's in when the CANCEL shows up πŸŽ€

First up, the UAS gets a CANCEL while it's in the Early state 🌈 In this case, it immediately sends a 487 for the INVITE, and the dialog moves over to the Terminated state. Bye, dialog πŸ‘‹

The other case is when the UAS receives a CANCEL while it's in the Confirmed state πŸ’– Here, the dialog state transition does not happen, because the UAS has already sent a final response to the INVITE that the CANCEL is aimed at πŸ™ˆ (Okay, bestie, here's the tea πŸ«–: because of how the UAC behaves, a UAS that gets a CANCEL in the Confirmed state can expect a BYE to arrive right away and then move to the Terminated state. But the UAS's state doesn't actually transition until it really receives that BYE πŸ₯Ή)


Appendix D. Notes on the Request in the Mortal State

this part is giving "pay close attention" energy 🚨 It describes the UA's behavior in the Mortal state, and it needs careful reading πŸ“– Also, every transaction completes independently of the others, following the principle of RFC 3261 [1] 🫢

In the Mortal state, only a BYE can be accepted 🎯 Every other message in the INVITE dialog usage gets responded to with an error πŸ™…β€β™€οΈ BUT (and this is the important part πŸ’…) sending ACK and running the authentication procedure for BYE both still happen in this state ✨ (Handling messages for multiple dialog usages is out of scope for this document, so for more info, check out RFC 5057 [6] πŸ“š)

ACK for error responses is handled by the transaction layer, so it has nothing to do with the dialog state 🧚 ACK for 2xx responses is different, because it's a request that's newly generated by a TU πŸ”₯ Even so, the ACK for 2xx and the ACK for error responses are both part of the INVITE transaction, even though they're handled differently (Section 17.1.1.1, RFC 3261 [1]) πŸ’œ So the INVITE transaction gets completed by the three-way handshake, which includes ACK, even in the Mortal state 🌸

Now for actual implementation stuff πŸ› οΈ The UA needs to keep the INVITE dialog usage around until the Mortal state finishes, so it can still send ACK for a 2xx response while in the Mortal state πŸ’– If a 2xx to INVITE is received in the Mortal state, the INVITE dialog usage gets extended to 64*T1 seconds after the 2xx is received, to deal with a possible 2xx retransmission ⏳ (The 2xx retransmission lasts 64*T1, so the UA needs to be ready to handle retransmissions for that whole duration πŸ₯Ή) BUT, the UA shall send an error response to other requests, since the INVITE dialog usage in the Mortal state is only kept around for sending ACK for 2xx πŸ™…β€β™€οΈ

The BYE authentication procedure shall be processed in the Mortal state πŸ” When authentication is requested by a 401 or 407 response, the UAC resends the BYE with appropriate credentials πŸŽ€ And the UAS handles retransmissions of the BYE that it requested authentication for πŸ¦‹

Appendix E. Forking and Receiving New To Tags ✨

okay so this section is all about what the TU does when it gets multiple responses with different To tags to the ini-INVITE πŸ’…

When an INVITE forks inside a SIP network, the TU can end up with several responses to the ini-INVITE that have different To tags (see Sections 12.1, 13.1, 13.2.2.4, 16.7, 19.3, etc., of RFC 3261 [1]). If the TU gets multiple 1xx responses with different To tags, the original DSM forks and a new DSM instance gets created. So now you have multiple early dialogs, bestie πŸ¦‹

If one of those early dialogs gets a 2xx response, it naturally moves to the Confirmed state ✨. No DSM state transition happens for the other early dialogs, and their sessions (early media) terminate. The UAC's TU ends the INVITE transaction 64*T1 seconds after it receives the first 2xx response. Also, all mortal early dialogs that don't transition to the Established state get terminated (see Section 13.2.2.4 of RFC 3261 [1]). When we say "mortal early dialog," we mean any early dialog that the UA will terminate when another early dialog is confirmed. Iykyk πŸ™ˆ

Here's an example sequence where two 180 responses with different To tags come in, and then a 200 response for one of the early dialogs (dialog A) arrives. The dotted lines (..) are just auxiliary lines showing the influence on dialog B 🌸

                                   UAC
                    dialog(A)       |    INVITE F1
                     Pre o          |------------------------->
                         |          |    100 F2
                         |          |<-------------------------
                         |          |    180(To tag=A) F3
                     Ear |          |<-------------------------
          dialog(B)      |          |
      forked new DSM     |          |    180(To tag=B) F4
          Ear o..........|..........|<-------------------------
              |          |          |
              |          |          |    200(A) F5
   terminate->|.....Mora |..........|<-------------------------
     early    |          | ^        |    ACK(A) F6
      media   |      Est | |        |------------------------->
              |          | |        |
              |          | |64*T1   |
              |          | |(13.2.2.4 of RFC 3261 [1])
              |          | |        |
              |          | |        |
              |          | V        |
              o..........|.(terminate INVITE transaction)
          terminated     |          |
           dialog(B)     |          |
                         |          |

         Figure 4: Receiving 1xx responses with different To tags

So the DSM sits inside a SIP TU here. When the provisional response with a different To tag (F4 180(To tag=B)) shows up, the DSM forks and early dialog B is generated. Then 64*T1 seconds later, dialog A gets a 200 OK. Dialog B never transitions to the Established state, so it terminates. Bye, dialog B πŸ‘‹

Next up: a TU that gets multiple 2xx responses with different To tags. When a mortal early dialog that didn't match the first 2xx response the TU received gets another 2xx response matching its To tag before the 64*T1 INVITE transaction timeout, its DSM transitions to the Confirmed state. However, the session on that mortal early dialog gets terminated once the TU receives the first 2xx to establish a dialog, so no session is established for the mortal early dialog. So when the mortal early dialog receives a 2xx response, the TU sends an ACK and then, right after, the TU usually sends a BYE to terminate the DSM. (In special cases, e.g., if a UA intends to establish multiple dialogs, the TU may not send the BYE.) The handling of the second early dialog after the 200 for the first dialog is pretty appropriate for a typical device, like a phone πŸ“± It's important to note that what's shown is a typical useful action and not the only valid one. Some devices might want to handle things differently. For instance, a conference focus that has sent out an INVITE that forks may want to accept and mix all the dialogs it gets. In that case, no early dialog is treated as mortal πŸŽ€

Below is an example sequence where two 180 responses with a different To tag come in, and then a 200 response for each of the early dialogs arrives.

                                UAC
                 dialog(A)       |    INVITE F1
                  Pre o          |----------------------->
                      |          |    100 F2
                      |          |<-----------------------
                      |          |    180(To tag=A) F3
         dialog(B)   Ear |          |<-----------------------
     forked new DSM      |          |    180(To tag=B) F4
          Ear o..........|..........|<-----------------------
              |          |          |
              |          |          |    200(A) F5
   terminate->|.....Mora |..........|<-----------------------
     early    |          | ^        |    ACK(A) F6
      media   |      Est | |        |----------------------->
              |          | |64*T1   |
              |          | |        |    200(B) F7
         Mora |..........|.|........|<-----------------------
              |          | |        |    ACK(B) F8
          Est |..........|.|........|----------------------->
              |          | |        |    BYE(B) F9
         Mort |..........|.|........|----------------------->
          ^   |          | |        |    200(B) F10
          |   |          | |        |<-----------------------
          |Timer K       | |        |
          |   |          | V        |
          |   |          | (terminate INVITE transaction)
          V   |          |          |
         Morg o          |          |
                         |          |

     Figure 5: Receiving 1xx and 2xx responses with different To tags

Here's another one, when a TU gets multiple 200 responses with different To tags before the 64*T1 timeout of the INVITE transaction, and there's no provisional response at all. Even though the TU never got a provisional response, it still needs to process the 2xx responses (see Section 13.2.2.4 of RFC 3261 [1]). In that case, the DSM state is forked at the Confirmed state. Then the TU sends an ACK for the 2xx response and, right after, the TU usually sends a BYE. (In special cases, e.g., if a UA intends to establish multiple dialogs, the TU may not send the BYE.)

                                 UAC
                  dialog(A)       |    INVITE F1
                   Pre o          |----------------------->
                       |          |    100 F2
                       |          |<-----------------------
                       |          |    180(To tag=A) F3
                   Ear |          |<-----------------------
                       |          |
                       |          |    200(A) F4
                  Mora |..........|<-----------------------
                       | ^        |    ACK(A) F5
                   Est | |        |----------------------->
                       | |        |
       dialog(B)       | |64*T1   |
   forked new DSM      | |        |    200(To tag=B) F6
       Mora o..........|.|........|<-----------------------
            |          | |        |    ACK(B) F7
        Est |..........|.|........|----------------------->
            |          | |        |    BYE(B) F8
       Mort |..........|.|........|----------------------->
        ^   |          | |        |    200(B) F9
        |   |          | |        |<-----------------------
        |   |          | V        |
        |Timer K       | (terminate INVITE transaction)
        |   |          |          |
        V   |          |          |
       Morg o          |          |
                       |          |

         Figure 6: Receiving 2xx responses with different To tags

Now the option tag 100rel (RFC 3262 [5]) is required by a 180. 🧚 If a forking proxy supports 100rel, it transparently transmits to the UAC a provisional response that contains a Require header with the value of 100rel. When the UAC gets a provisional response with 100rel, it establishes early dialog (B) and sends PRACK (Provisional Response Acknowledgement). (And yes, every transaction still completes independently of the others.) Just like in Figure 4, early dialog (B) terminates at the same time the INVITE transaction terminates. If a proxy doesn't support 100rel, the provisional response gets handled the usual way: a provisional response with 100rel is discarded by the proxy and never sent on to the UAC πŸ™…β€β™€οΈ

                                UAC
                 dialog(A)       |    INVITE F1
                  Pre o          |------------------------->
                      |          |    100 F2
                      |          |<-------------------------
                      |          |    180(To tag=A) F3
                  Ear |          |<-------------------------
                      |          |    200(A) F4
                 Mora |..........|<-------------------------
                      | ^        |    ACK(A) F5
                  Est | |        |------------------------->
       dialog(B)      | |        |
   forked new DSM     | |        |    180(To tag=B) w/100rel F6
       Ear o..........|.|........|<-------------------------
           |          | |        |    PRACK(B) F7
           |          | |        |------------------------->
           |          | |        |    200(B,PRACK) F8
           |          | |        |<-------------------------
           |          | |64*T1   |
           |          | |(13.2.2.4 of RFC 3261 [1])
           |          | |        |
           |          | |        |
           |          | |        |
           |          | V        |
           o..........|.(terminate INVITE transaction)
       terminated     |          |
        dialog(B)     |          |
                      |          |

         Figure 7: Receiving 1xx responses with different To tags
   when using the mechanism for reliable provisional responses (100rel)

Authors' Addresses 🫢

Miki Hasebe
NTT-east Corporation
19-2 Nishi-shinjuku 3-chome
Shinjuku-ku, Tokyo  163-8019
JP

EMail: hasebe.miki@east.ntt.co.jp


Jun Koshiko
NTT-east Corporation
19-2 Nishi-shinjuku 3-chome
Shinjuku-ku, Tokyo  163-8019
JP

EMail: j.koshiko@east.ntt.co.jp


Yasushi Suzuki
NTT Corporation
9-11, Midori-cho 3-Chome
Musashino-shi, Tokyo  180-8585
JP

EMail: suzuki.yasushi@lab.ntt.co.jp


Tomoyuki Yoshikawa
NTT-east Corporation
19-2 Nishi-shinjuku 3-chome
Shinjuku-ku, Tokyo  163-8019
JP

EMail: tomoyuki.yoshikawa@east.ntt.co.jp


Paul H. Kyzivat
Cisco Systems, Inc.
1414 Massachusetts Avenue
Boxborough, MA  01719
US

EMail: pkyzivat@cisco.com
πŸ“¦ where this page comes from