This section provides a new design for Kamailio's Dialog module in order to make it more powerful and suitable. The main target is proper handling of forking dialogs and special rare cases, such as when spirals occur.
Current Dialog module lacks some important features, such as when parallel or serial forking occurs. For an initial INVITE just a single dialog entry is created by the Dialog module which tries to store dialog data as CSeq, remote target, To-tag and so on. Unfortunately, this is not enough when forking occurs as different dialogs (or early-dialogs) are generated, each of them possibly holding a different CSeq value (i.e., if a forking branch requires 100rel) and having different To-tags and remote-targets (along with other dialog-specific information). Correct tracking of these values is not only needed in order to display statistics properly but also to make MI-triggered session termination work.
At the same point, from the point of view of the UAC it shouldn't matter how many forks the proxy (or a forking proxy behind the proxy, i.e., a remote proxy) has generated since it should be counted as just one dialog for such client. For example, imagine a proxy that wants to limit the number of concurrent calls made by a specific user. In that case, it shouldn't matter how many branches the proxy (either local or remote) generates.
The new module design proposed here makes use of two different tables:
When a dialog is started upon receipt of an INVITE in the proxy, a single entry in dialog_in table is created with state = “proceeding” (in the dlg_onreq() function).
However, there is a constrain that prevents the creation of a new entry in dialog_in table: If the INVITE's From-tag and Call-ID match an existing entry in the dialog_in table then no new entry will be added. Basically this means that a SIP spiral will be observed as a single dialog which is reasonable since spiraled INVITEs, while triggering the creation of new transactions on the same proxy, still belong to the same dialog (i.e., the same SIP dialog endpoints).
In order to match responses to requests, the Dialog module registers on the TM module callback event DLGCB_RESPONSE_OUT_N during dialog creation. Along with the registration, a reference to the dialog structure is be passed so that during execution of the response callback, the associated dialog can be easily modified without further matching effort.
Definition of the dialog_in table:
Column | Description | Possible values | Notes |
---|---|---|---|
id | Table primary key as usual | ||
hash_entry | Dialog hash entry information | ||
hash_id | Dialog hash id information | ||
dialog_id | Dialog ID | Required for the case of concurrently confirmed calls | |
callid | Value of Call-ID header | ||
from_tag | Value of tag param in From header | ||
from_uri | Value of the URI in From header | Optional | |
caller_original_cseq | Original value of CSeq in the INVITE | ||
ruri | Value of ruri just when dlg_manage is invoked | Optional | |
caller_contact | Value of URI in INVITE's Contact header | ||
caller_route_set | Route records from caller side (proxy to caller) | Optional | |
caller_sock | Socket in which the INVITE was received | ||
state | Current status of the dialog from the caller point of view | 1,2,3,4 | Codes explained below |
start_time | Start time of the dialog | ||
timeout | Timeout value set for this dialog | ||
toroute | Index of the route to be executed at timeout |
The dialog_out table contains one entry for every early dialog generated for a given dialog_in entry (i.e., SIP dialog). The entries live there as long as the dialog is in the early state. As soon as it transitions to confirmed or terminated, all but one dialog_out entry turns obsolete and will be destroyed (after some seconds due to TM timer).
The dialog_out entries related to a specific dialog_in entry are changed (i.e., added, deleted, and modified) when a response is forwarded by a proxy towards the caller. (Note that this is unlike the current implementation w.r.t. the point in the time when callee data is stored: While this currently only happens when a dialog is confirmed, the new implementation will start filling as soon as possible, i.e., when a early-transitting response contains callee data already.) The forwarding occasions (namely, when 100+ and [23456]XX responses are received) will suffice because these perfectly match the times when dialog state needs to be adjusted.
Definition of the dialog_out table:
Column | Description | Possible values | Notes |
---|---|---|---|
id | Table primary key as usual | ||
dialog_id | Dialog ID | This values must reference an entry in dialog_in table | |
to_tag | Value of tag param in To header | ||
caller_cseq | Value of CSeq in the caller for this (early-)dialog | This value could be different for each early dialog | |
callee_cseq | Value of CSeq in the callee for this (early-)dialog | ||
callee_contact | Value of URI in Contact header of the response (if present) | ||
callee_route_set | Route records from callee side (proxy to callee) | Optional | |
callee_sock | Socket from which the INVITE was relayed | ||
dflags | Flags for this (early-)dialog | Useful for modules on top of Dialog module |
The possible dialog state values are:
Note that there is no state for “confirmed but ACK pending”. According to RFC 3261, a dialog is confirmed when the 200 is received, no need to wait for the ACK. However if the ACK is not received by the UAS within 32 seconds then the UAS would terminate the dialog by sending a BYE.
When a response is forwarded by a proxy, the TM module invokes the Dialog module callback TMCB_RESPONSE_OUT_N for the previously generated dialog. For security reasons (i.e., forgery preventation), the callback ignores the response if its Call-ID or From-tag doesn't match the entry in the dialog_in table with same dialog_id. If it is a valid response, the following algorithm is conducted to track dialogs appropriately.
When an in-dialog request arrives it's matched based on the RR cookie inserted in the original Record-Route header (so it will be present in the Route header of all the requests within same dialog). Legacy dialog matching (From-tag, To-tag, and Call-ID) is not valid for a proxy in scenarios in which a spiral occurs, as an in-dialog request would be matched twice for the same dialog. In order to enhance legacy matching mode to manage spirals correctly, the route-set would need to be stored on creation of the dialog structure and later compared on each in-dialog spiral hop. However, comparison of route sets proves to be difficult, not only for computational but also for several (mostly UA-induced) reasons:
Therefore, dialog matching based on RR cookies will be the preferred and, initially, only implemented method (of course, anyone is free and welcome to upgrade legacy matching mode).
An in-dialog BYE for a confirmed dialog sets the state to “terminated”. When the call state is “early” the UAC may send a BYE to terminate a specific early-dialog only (as opposed to sending a CANCEL which would terminate the whole INVITE transaction and all early-dialogs, that is, the entire session). In that latter case, the entire call's state is changed to “terminated” if only one early-dialog existed, i.e., no proxy forking occurred.
From the point of view of the client, a single dialog exists even if it forkes into various early-dialogs. Then, the script function or MI command should take into account just the entries in dialog_in table in order to know the number of dialogs for a specific profile.
The current module design allows to use callbacks based on dialog events (register_dlgcb()
). These callbacks must be invoked when the state of the dialog changes.
In order to allow users to perform custom actions when a request spirals (i.e., a SIP request is routed through a proxy multiple times), the new callback DLGCB_SPIRALED will be introduced. It will be triggered each time a proxy receives an INVITE request when the dialog has already been created (i.e., the INVITE is passing the proxy for the second, third, or any other subsequent time). Although DGLCB_SPIRALED does not reflect a change in dialog state, it is still useful and needed in certain occasions, e.g., when modules depend on it.
For a single request, multiple 200 responses may arrive at the same time, thereby resulting in the confirmation of multiple dialogs. Dialogs created this way will be denoted as concurrently confirmed calls followingly.
For each such established call, a separate dialog_in entry will be generated which allows module callback users to track them separately and according to their needs (instead of having to track them either together or not at all). However, because concurrently confirmed calls all point to the same dialog hash ID, and because that hash ID must be chosen during request processing, the dialog_in table uses a dialog ID to accommodate for multiple dialogs maintained under the same hash ID.
Once a concurrently confirmed call is created in the Dialog module, the new callback DLGCB_CREATED_CONCUR will be triggered. Users may register for these callbacks just like they may do for DLGCB_CREATED, i.e., without the need to pass an existing dialog structure to the register function.
Each (early-)dialog in dialog_out table can contain specific flags (dflags field). Meaning of such flags depends on other modules making usage of Dialog module. For example MediaProxy module could use these flags in order to force MediaProxy server just for certains (early-)dialogs (those detected as natted) and still using a convenient function like “engage_mediaproxy()” (which currently fails in forking cases as MediaProxy is applied to all or none of the dialogs).
This would involve a new functions setdflag()
, resetdflag()
and isdflagset()
. These functions can be invoked just in “onreply_route” (the reason is that during “route”, “branch_route” and “failure_route” there is no dialog info yet).
Through an MI function called dlg_end_dlg, Kamailio allows to terminate a dialog by means of sending out two locally generated BYE requests to the involved UAs. The future dialog module implemention should improve this functionality with regards to the following aspects:
Alice calls Bob and the proxy forks in parallel to both locations of Bob (Bob-1 and Bob-2):
dialog_in | hash_id | dialog_id | callid | from_tag | caller_contact | state |
---|---|---|---|---|---|---|
1111 | 1111a | abcd | ffff | sip:alice@home.org | proceeding |
dialog_in | hash_id | dialog_id | callid | from_tag | caller_contact | state |
---|---|---|---|---|---|---|
1111 | 1111a | abcd | ffff | sip:alice@home.org | early |
dialog_out | dialog_id | to_tag | caller_cseq | callee_cseq | callee_contact |
---|---|---|---|---|---|
1111a | bbb111 | 1 | (not set) | (not set) |
dialog_in | hash_id | dialog_id | callid | from_tag | caller_contact | state |
---|---|---|---|---|---|---|
1111 | 1111a | abcd | ffff | sip:alice@home.org | early |
dialog_out | dialog_id | to_tag | caller_cseq | callee_cseq | callee_contact |
---|---|---|---|---|---|
1111a | bbb111 | 1 | (not set) | (not set) | |
1111a | bbb222 | 1 | (not set) | sip:bob2@2.2.2.2 |
dialog_in | hash_id | dialog_id | callid | from_tag | caller_contact | state |
---|---|---|---|---|---|---|
1111 | 1111a | abcd | ffff | sip:alice@home.org | early |
dialog_out | dialog_id | to_tag | caller_cseq | callee_cseq | callee_contact |
---|---|---|---|---|---|
1111a | bbb111 | 1 | (not set) | (not set) | |
1111a | bbb222 | 2 | (not set) | sip:bob2@2.2.2.2 |
dialog_in | hash_id | dialog_id | callid | from_tag | caller_contact | state |
---|---|---|---|---|---|---|
1111 | 1111a | abcd | ffff | sip:alice@home.org | confirmed |
dialog_out | dialog_id | to_tag | caller_cseq | callee_cseq | callee_contact |
---|---|---|---|---|---|
1111a | bbb222 | 2 | (not set) | sip:bob2@2.2.2.2 |
dialog_in | hash_id | dialog_id | callid | from_tag | caller_contact | state |
---|---|---|---|---|---|---|
1111 | 1111a | abcd | ffff | sip:alice@home.org | confirmed |
dialog_out | dialog_id | to_tag | caller_cseq | callee_cseq | callee_contact |
---|---|---|---|---|---|
1111a | bbb222 | 2 | 101 | sip:bob2@2.2.2.2 |
dialog_in | hash_id | dialog_id | callid | from_tag | caller_contact | state |
---|---|---|---|---|---|---|
1111 | 1111a | abcd | ffff | sip:alice@home.org | terminated |
dialog_out | dialog_id | to_tag | caller_cseq | callee_cseq | callee_contact |
---|---|---|---|---|---|
1111a | bbb222 | 2 | 102 | sip:bob2@2.2.2.2 |
A complex case in which the INVITE is routed through a proxy twice and later serially forked to an IVR server. No new dialog_in entry is created upon the second (spiraled) receipt of same INVITE. However, custom modules which may still need to interact at this point may do so by means of the DLGCB_SPIRALED callback.
In the example scenario, Alice tries to call Bob:
Alice ----> P1 ----> P2 ----> P1 ----> Bob
However, Bob rejects the call and P2 decides to reroute it to an IVR server:
Alice ----> P1 ----> P2 ----> IVR
Let's inspect the behavior of the Dialog module in P1:
dialog_in | hash_id | dialog_id | callid | from_tag | caller_contact | state |
---|---|---|---|---|---|---|
1111 | 1111a | abcd | ffff | sip:alice@home.org | proceeding |
dialog_in | hash_id | dialog_id | callid | from_tag | caller_contact | state |
---|---|---|---|---|---|---|
1111 | 1111a | abcd | ffff | sip:alice@home.org | early | |
dialog_out | dialog_id | to_tag | caller_cseq | callee_cseq | callee_contact | |
1111a | aaaa | 1 | (not set) | sip:bob@1.2.3.4 |
dialog_in | hash_id | dialog_id | callid | from_tag | caller_contact | state |
---|---|---|---|---|---|---|
1111 | 1111a | abcd | ffff | sip:alice@home.org | early | |
dialog_out | dialog_id | to_tag | caller_cseq | callee_cseq | callee_contact | |
1111a | aaaaa | 2 | (not set) | sip:bob@1.2.3.4 |
dialog_in | hash_id | dialog_id | callid | from_tag | caller_contact | state |
---|---|---|---|---|---|---|
1111 | 1111a | abcd | ffff | sip:alice@home.org | confirmed | |
dialog_out | dialog_id | to_tag | caller_cseq | callee_cseq | callee_contact | |
1111a | bbbb | 1 | (not set) | sip:ivr@provider.com |
IMPORTANT: The new dialog_out has caller_cseq with value “1” (the original value) as no in-dialog request has been sent by the UAC to this branch. This is achieved by copying field caller_original_cseq from dialog_in table to the new entry under dialog_out table.
dialog_in | hash_id | dialog_id | callid | from_tag | caller_contact | state |
---|---|---|---|---|---|---|
1111 | 1111a | abcd | ffff | sip:alice@home.org | terminated | |
dialog_out | dialog_id | to_tag | caller_cseq | callee_cseq | callee_contact | |
1111a | bbbb | 2 | (not set) | sip:ivr@provider.com |
The following example scenario will try to illustrate the behavior for concurrently confirmed calls. It will be as simple as this:
Alice ----> proxy ----> Bob
Let's inspect the behavior of the Dialog module on the proxy:
Alice calls Bob and the proxy forks in parallel to both locations of Bob (Bob-1 and Bob-2):
dialog_in | hash_id | dialog_id | callid | from_tag | caller_contact | state |
---|---|---|---|---|---|---|
1111 | 1111a | abcd | ffff | sip:alice@home.org | proceeding |
dialog_in | hash_id | dialog_id | callid | from_tag | caller_contact | state |
---|---|---|---|---|---|---|
1111 | 1111a | abcd | ffff | sip:alice@home.org | early | |
dialog_out | dialog_id | to_tag | caller_cseq | callee_cseq | callee_contact | |
1111a | gggg | 1 | (not set) | (not set) |
dialog_in | hash_id | dialog_id | callid | from_tag | caller_contact | state |
---|---|---|---|---|---|---|
1111 | 1111a | abcd | ffff | sip:alice@home.org | early | |
dialog_out | dialog_id | to_tag | caller_cseq | callee_cseq | callee_contact | |
1111a | gggg | 1 | (not set) | (not set) | ||
1111a | hhhh | 1 | (not set) | (not set) |
dialog_in | hash_id | dialog_id | callid | from_tag | caller_contact | state |
---|---|---|---|---|---|---|
1111 | 1111a | abcd | ffff | sip:alice@home.org | confirmed | |
dialog_out | dialog_id | to_tag | caller_cseq | callee_cseq | callee_contact | |
1111a | gggg | 1 | (not set) | sip:bob1@1.1.1.1 | ||
1111a | hhhh | 1 | (not set) | (not set) |
dialog_in | hash_id | dialog_id | callid | from_tag | caller_contact | state |
---|---|---|---|---|---|---|
1111 | 1111a | abcd | ffff | sip:alice@home.org | confirmed | |
1111 | 1111b | abcd | ffff | sip:alice@home.org | confirmed | |
dialog_out | dialog_id | to_tag | caller_cseq | callee_cseq | callee_contact | |
1111a | gggg | 1 | (not set) | sip:bob1@1.1.1.1 | ||
1111b | hhhh | 1 | (not set) | sip:bob2@2.2.2.2 |
dialog_in | hash_id | dialog_id | callid | from_tag | caller_contact | state |
---|---|---|---|---|---|---|
1111 | 1111a | abcd | ffff | sip:alice@home.org | confirmed | |
1111 | 1111b | abcd | ffff | sip:alice@home.org | terminated | |
dialog_out | dialog_id | to_tag | caller_cseq | callee_cseq | callee_contact | |
1111a | gggg | 1 | (not set) | sip:bob1@1.1.1.1 | ||
1111b | hhhh | 1 | (not set) | sip:bob2@2.2.2.2 |
Once the transaction layer's post-mortem timeout triggers, the dialog module will cleanup the terminated dialog_in and dialog_out entries. Meanwhile, the other concurrently confirmed call remains:
dialog_in | hash_id | dialog_id | callid | from_tag | caller_contact | state |
---|---|---|---|---|---|---|
1111 | 1111a | abcd | ffff | sip:alice@home.org | confirmed | |
dialog_out | dialog_id | to_tag | caller_cseq | callee_cseq | callee_contact | |
1111a | gggg | 1 | (not set) | sip:bob1@1.1.1.1 |