Login | Register


Documentation -> Tutorials -> Call Accounting

1.  Overview

SIP call accounting is a sensitive and challenging task. The flexibility and complexity of OpenSIPS escalates the need to understand the complete details of the accounting approaches. This tutorial explains all the elements and notions about accounting, as well as how to take full advantage of the powerful accounting engine provided by OpenSIPS.

Concepts such as accounting events (missed calls, failed calls) , the accounting scope (transaction, dialog), accounting backends (database, log, radius) are explained and covered by full script examples. More complex scenarios including advanced features like extra accounting (extending the CDR format and collecting information from the entire call), and multi-leg accounting (multiple CDR records per SIP call) are also covered.

Everything in this tutorial is backed up with practical examples which you can run and dissect in order to get a better picture about how accounting works, and what are the best accounting options for your needs. Most of the features presented in this tutorial are available in versions starting with OpenSIPS 2.2, where there has been a major rework affecting the accounting module. Nevertheless, this rework continued with OpenSIPS 2.3 when comes to simplifying the user experience in regards to the scripting.

2.  Accounting concepts

2.1  Accounting scopes

The accounting may have different scopes and it may be in-line with different SIP elements :

  • SIP message level accounting: the generated accounting record covers only the currently processed SIP request; this is also known as manual accounting, where the CDR is generated on the spot by using one of the script functions acc_log_request(), acc_db_request(), acc_aaa_request() or acc_evi_request() depending on the backend used;
  • SIP transaction level accounting: the generated accounting record covers a SIP transaction (the duration and data from the SIP request and all its replies); such accounting is used when OpenSIPS acts as a stateful SIP proxy; this accounting is armed/set during the request processing, but the accounting record itself will be generated when the SIP transaction (including all its branches) will be completed;
  • SIP dialog level accounting: the generated accounting record covers a whole SIP call/dialog; such accounting record is also known as CDR (Call Detail Record). This record, in addition to the transaction-scope accounting records, may contain information from the dialog level (call duration, BYE reason, RTP stats); In terms of usage, the dialog accounting is armed/set while handling the initial INVITE request; the CDR itself will be generated when the SIP dialog ends(that is when all the messages belonging to a dialog have been processed, including the replies for BYE messages);

To conclude, if you do SIP stateless processing or if you need to manually account some really custom corner-cases, you should use the SIP message level accounting. Otherwise, if in stateful mode, for non-INVITE traffic, the SIP transaction level accounting gives you the best results - a single accounting record aggregating all the transaction related information. For INVITE based sessions, the SIP dialog level accounting is the easiest and most powerful way to get ready to use CDRs.

2.2  Accounting events

OpenSIPS can account different events during the accounting scope, such as:

  • success event. Such events generate an accounting record when:
    • in transaction scope - the transaction when successfully completed (with a 2xx reply):
  • in dialog scope - the call was successfully established:
  • missed call event. Such events may occur only during the dialog setup phase (during the lifetime of the transaction for the initial INVITE), when a call attempt (or branch) was not established, as the UAS destination rejected or declined the call; There can be multiple such events per a single SIP call (due SIP forking). Note that a SIP call generating an accounting success event may also generate one or more missed call event because of the SIP branches (call attempts) that failed during serial or parallel forking; The missed call events are also known as UAS-side accounting (as they are related to the relation between OpenSIPS and callee/UAS side).
  • failed transaction event. Such event is generated only in the transaction scope, when a transaction fails (reply code >=300) on the UAC side of OpenSIPS - this is a complementary event for the successful transaction event.

2.3  Accounting backends

When doing accounting, one can choose between a variety of engines or even combine them together:

  • log: the accounting shall be logged wherever you choose to log it, via syslog, either in the same location as the OpenSIPS logs or in any other location you choose; backend
  • db(database): use one of the many database backends currently implemented in OpenSIPS(mysql, postgres, sqlite etc.);
  • aaa: account your data using an aaa protocol(currently only radius is available);
  • evi: information shall be accounted using the event interface;

3.  Extending the accounting engine

3.1  Extra accounting fields

This mechanism allows you to extend the accounting record field by adding custom fields to it (with data collected anytime during the scope of the accounting).
The extra_fields module parameter allows the definition of a list of new fields to be added to the format of the accounting record. During the script processing, those fields may be populated anywhere and anytime as you are still in the scope of that accounting action.

There is a special script variable defined for this mechanism called $acc_extra which can be populated with whatever you want; the $acc_extra variable is persistent across the whole accounting scope - for example, if you do transaction based accounting, you can collect data (to be pushed into the extra accounting fields) during the request handling, in reply route or during a call forking in failure route.
NOTE that you need to take care and adjust your backend (database or radius) to accommodate the extra added values!!

3.2  Multi-leg accounting

There are SIP scenarios where there is a need to get multiple accounting records for a single SIP call. Typically this happens when implementing call redirect or call forward and the actual SIP call consists out of several call legs (logical calls between SIP entities).
The leg_fields module parameter defines a list of new fields that will be different from leg to leg (all the other fields - the default and extra accounting ones - will be the same for all the legs). Each set of leg_fields values will translate into a separate leg for the call.

As said, this shall be useful when logically, there are multiple legs inside a SIP call - like A calls to B and B redirects the call to C, meaning that there are two legs for this call: A to B and B to C. It is the script writer's job to decide whether a new leg has to be created by using acc_new_leg() script function. The variable used to hold the per-leg fields is $acc_leg - anytime, in the context of that leg, you can set the values for the per-leg fields.
NOTE that you need to take care and adjust your backend (database or radius) to accommodate the extra added values!!

4.  Examples

4.1  Basic accounting

The OpenSIPS default config file provides a simple accounting support. This will be used as a starting point in our quest to understand and script more and more complex accounting scenarios.

In this script, we want to get accounting records only for call related requests - for INVITE and BYE transactions. So, the accounting is done only at transaction level and the following accounting events are configured:

  • success event for the INVITE transactions
  • success event and failed transaction event for the BYE transactions - we want an accounting record no matter response code we get for the BYE.

The syslog backend shall be used for accounting - this has the keyword log.

Now, let's walk through the opensips.cfg and explain step by step all the accounting related actions.

In the routing section of the script the do_accounting() function is used for setting the accounting process (as events, scope and backend). In all case the log backend is used, which means that all the accounting records will be pushed as logs. This script is logging to syslog service, using log facility LOG_DAEMON (see log_facility ) with log level L_NOTICE (see log_level).

When processing the SIP requests in the request route, when handling the sequential request, the do_accounting() function is used (see line 127) to trigger accounting records on success and failed transactions(reply code >= 300) events for all the BYE transactions:

        if (has_totag()) {
                # sequential requests within a dialog should
                # take the path determined by record-routing
                if (loose_route()) {

                        if (is_method("BYE")) {
                                # do accounting, even if the transaction fails

Here is an example of how the accounted lines should look line in your syslog

ACC: transaction answered: timestamp=1474540760;method=BYE;\

If the failed option is not used, the do_accounting() will not trigger the failed transactions event and the above record will not be generated (since the transaction completes with a 400 reply code).

Also in the request route, when handling the initial requests, for INVITE requests we do (see line 194):

        # account only INVITEs
	if (is_method("INVITE")) {

Since no flag is specified, only success events will be accounted for the transaction:

ACC: transaction answered: timestamp=1474540997;method=INVITE;\

The last do_accounting() usage in the script (line 231) is for triggering the missed call events for the calls (INVITE requests) sent to our registered users.

        # when routing via usrloc, log the missed calls also

Once again, this is the full OpenSIPS config file we used for this example.

4.2  CDR-based and serial forking accounting

In this new example, we extend the accounting functionality offered by the previous script in order to additionally achieve :

  • enable dialog level accounting so we can get CDRs (as accounting records)
  • enable multiple missed calls events during SIP serial forking. Let's take the case when there is a call for user Alice but Alice has multiple contacts registered (via SIP registration), like Contact1, Contact2 and Contact3. The order for serial forking should be Contact1, Contact2 and Contact3. That is first time we try to call Contact1, if Contact1 does not respond Contact2 shall be tried and the last try should be made to Contact3. Last, but not least, if the call is to established (to any of the contacts), the call will be accounted.

To achieve this behavior we do the following changes to the accounting functions:

  • add dialog scope (cdr keyword) when setting the success event for the INVITE requests
  • as we do accounting at dialog level (we get CDRs directly), there is no need to separately account the BYE transaction, so we remove any accounting for them
  • enable missed call event also in failure route, so we can get such events during the sequential steps of the serial forking.

This is the config file corresponding to this scenario. All below explanations are relative to this script.

First of all, in order to be able to use the CDR engine, dialog module needs to be loaded (we do it here in the simplest possible way):

loadmodule "dialog.so"
modparam("dialog", "db_mode, 0)

Now, in terms of scripting we remove the do_accounting() for the BYE transactions (as we do accounting at dialog level, not transaction level any more) - see the sequential request block, line 127 where the accounting call was stripped out.

To get directly CDR as accounting records, when doing do_accounting() (line 193) for initial INVITEs (which create SIP calls/dialogs), we need to switch it from transaction scope to dialog scope, by using the cdr keyword to the flags:

        # account only INVITEs
	if (is_method("INVITE")) {
		do_accounting("log", "cdr");

Keep in mind that the CDR flag affects only the calls(dialogs).

To enable serial forking (for all the registered contacts) the following lines were added after a successful lookup into location table. The code is serializing the found contact based on their priority (Q parameter) - first we send the call to the higher priority contacts and in case of failure (no call established), we serial fork the call to the next contacts with lower priority (see serialize_branches() description) . This re-attempting (serial forking) is done until there are no more contacts left.


In the failure route the following lines were added:


        # if we've got any more branches arm the failure route
        if ($rc != 2) {

        if (!t_relay()) {
                send_reply("500","Internal Error");

If do_accounting() would not have been called again, in case of failure, only the first missed call would have been accounted. The flag resets each time a missed call is accounted. The next_branches() function call will get the next destination for us. If we still have contacts to call to we arm the failure route again in order to be able to send the call to the next destination.

A missed call event will be generated for each destination that did not accept the call:

ACC: call missed: timestamp=1474637733;method=INVITE;\
  from_tag=1931529157;to_tag=;call_id=182969767;code=408;reason=Request Timeout

The reply code and the reason may change depending on the scenario. If at least one of the contacts answered and the call is established, the CDR (as success event) will be generated after the call ended:

ACC: call ended: created=1474639265;call_start_time=1474639281;\

Here, besides the basic information that is present in every accounting record we can see the the additional fields call_start_time, and the call duration both in seconds and milliseconds.

Once again, this is the full OpenSIPS config file we used for this example.

4.3  Extra fields accounting

There are cases when you may need to extend your accounting capabilities and to add some additional custom values to the accounting records (in addition to the default fields). This is called extra accounting - shortly, you define some new accounting fields and set what values is to be placed into them.

This is the full OpenSIPS config file used for this example.

Starting from the previous script, we try to add extra accounting information about the IP addresses where the call was originated from and terminated to. First of all we need to define the additional data that will be added to our accounted information in the modparam section using the extra_fields parameter

   modparam("acc", "extra_fields", "log: src_ip; dst_ip")

The following definition would also be valid

   modparam("acc", "extra_fields", "log: src_ip->src_ip; src_ip->dst_ip")

The first part of the declaration is the backend for which the extra fields are defined, in this case the log backend. The definitions are in form of script-tag -> backend-tag. The script-tag is the tag to be used in OpenSIPS script to reference the $acc_extra variable, which will hold all our extra values. The scope of the variable depends on the accounting type used. If the cdr flag is used when calling do_accounting(), the variable will be visible for the whole duration of the dialog, else it will be visible as long as the transaction will be visible. The backend-tag is where the values will be stored in the accounting backend ( for log it is a display label, for db it is a column name or for aaa it is an RADIUS/DIAMETER AVP name).

In terms of scripting, all we need to do is to set the $acc_extra variable with the values we what. In the current scenario we set the IPs as follow:

  • for the originating IP of the call we consider the source IP of the INVITE (initial request), so in request route, when handling initial SIP requests, we do (see line 224):
        $acc_extra(src_ip) = $si;
  • for the termination IP of the call we consider the source IP of final SIP reply (>=200), so in the onreply route, when handling SIP replies, we do (see line 268):
    if ( $rs >= 200 )
        $acc_extra(dst_ip) = $si;

The CDR for the current call will contain the new extra values, as we set them

ACC: call ended: created=1474899444;call_start_time=1474899448;\

Here it is the full OpenSIPS config file used in this example.

4.4  Multi-leg accounting

Sometimes the extra values are not enough. One such scenario is when user Alice calls to user Bob, but Bob doesn't not respond and he's got a redirect to user Charlie. In this case we have two logical legs, from Alice to Bob and from Bob to Charlie. And we want to get separate accounting information for each of these legs. In OpenSIPS, this is called multi-leg accounting or per-leg accounting.

In our example, we want to account the caller and callee usernames for each leg (as they we differ for the two legs). Depending on the accounting backend, when using multi-leg accounting, a single accounting records may translate into multiple backend records (one for each leg). For example, if using db backend, you will have one row per leg in the accounting table (this is because in SQL the format of the table is fixed, and it cannot vary with the number of legs).

This is the full OpenSIPS config file used for this example.

The modparam declaration for leg_fields is very similar to the extra_fields

modparam("acc", "leg_fields", "log: caller; callee")

The same is the case for $acc_leg variable. The only difference is that this variable can be indexed using the leg number. No index means the current leg. You can also get the number of the last leg using $acc_current_leg variable. New additional legs can be created with acc_new_leg() function.

In the script, everything that concerns the lookup() function was moved into a route, in order to use it multiple times. For the initial call, we'll get the URI of the caller from the From header. The URI of the callee is in the R-URI:

      $acc_leg(caller) = $fu;
      $acc_leg(callee) = $ru;

In case our call leg to Bob (as described above) fails, script execution will resume from the failure_route, where we will do serial forking to the redirect URI (to Charlie). In failure route, the redirected URI is resolved in the same way we did with the first uri, using lookup function. Now the leg Bob to Charlie is about to start, so we'll create a new leg and populate the $acc_leg variables accordingly for this new leg. The caller URI will be the callee URI of the previous leg, while the callee U will be the new redirect URI:

    if ( $avp(redirect_uri)!=NULL ) {
        # set the new destination
        $ru = $avp(redirect_uri);

        # create a new call leg
        # new caller is the callee of the previous leg
        $acc_leg(caller) = $(acc_leg(callee)[-2]);
        # new callee is the new destination
        $acc_leg(callee) = $ru;


For one redirect, we will have two account lines. One for the missed call from Alice to Bob

ACC: call missed: timestamp=1474899448;created=1474899444;setuptime=4;method=INVITE;\
  from_tag=1766231375;to_tag=;call_id=604787970;code=408;reason=Request Timeout;\

and the CDR record, with both legs, Alice to Bob and Bob to Charlie:

ACC: call ended: created=1474899444;call_start_time=1474899448;duration=5;ms_duration=4762;\

If using db backend two rows will be generated, one with the leg from Alice to Bob and one with the leg from Bob to Charlie. The non-leg fields will be the same in all the legs:

|   created  | .... |reason|   src_ip   |   dst_ip   | caller | callee  |
| 1474899444 | .... |  OK  | | | Alice  |   Bob   |
| 1474899444 | .... |  OK  | | |  Bob   | Charlie |

Here it is the full OpenSIPS config file used in this example.

Page last modified on November 03, 2016, at 04:50 PM