Integrating Canyan Rating with OpenSIPS

by Aleksandar Sošić April 29, 2020

The rating system is a critical component in any business, especially when real-time features are a strict requirement to ensure business continuity and congruence of transactions. Any compromise to availability, integrity, and authentication regarding the rating system makes a huge impact on the services provided.

Canyan aims to address these challenges with a cloud-native scalable solution, easily deployable, and easily usable. It has been designed to work atomically ensuring the system status is always consistent, reproducible, and coherent. Asynchronous processing of no real-time, consolidation events, prioritization, and time-boxed tasks provide the basics to ensure lightning-fast transaction processing without compromises.

canyan rating integration with opensips

Ease of use is addressed with comprehensive documentation, examples and high-quality software with high score of test coverage.

OpenSIPS

OpenSIPS is an Open Source SIP proxy/server for voice, video, IM, presence and any other SIP extensions.

OpenSIPS is a multi-functional, multi-purpose signaling SIP server used by carriers, telecoms or ITSPs for solutions like Class4/5 Residential Platforms, Trunking / Wholesale, Enterprise / Virtual PBX Solutions, Session Border Controllers, Application Servers, Front-End Load Balancers, IMS Platforms, Call Centers, and many others - see the full Set of Features.

OpenSIPS is recommended for any kind of SIP scenario / service by:

  • the high throughput - tens of thousands of CPS, millions of ‏simultaneous calls (see official tests)

  • the flexibility of routing and integration - routing script for implementing custom routing logics, several interfacing APIs (see the Manual)

  • the effective application building - more than 120 modules to provide features, for SIP handling, for backend operations, for integration, for routing logics (see List of Modules)

Prerequisites

The Canyan Rating integration repository contains everything we need for understanding the usage of Canyan Rating with OpenSIPS. The Makefile in that repository provides us with two shortcut commands: make docker-start and make docker-stop.

Also, there is a make test-opensips command that we will look at in a while to understand what is happening behind the scenes.

Scope

The goal of this blog post is the integration of the authorization, begin and end transaction methods of the Rating Agent in a OpenSIPS instance: OpenSIPS needs to perform authorization for every outgoing and incoming call, getting all the information needed to perform or not the given calls. Also, the beginning and end of a call need to be signalized to the Rating Agent.

The Rating Agent is listening for http requests with REST and GraphQL APIs. In OpenSIPS we will use the REST Module in combination with Asynchronous Statements to avoid binding OpenSIPS workers with synchronous http calls.

OpenSIPS Configuration

The integration repository contains an example configuration file for the integration with Canyan Rating.

Please note that the OpenSIPS configuration file in this example is simple and basic for the purpose of this tutorial and should not be used in production!

Includes and Modparams

Let's start loading the modules needed for this example:

loadmodule "dialog.so"
loadmodule "event_route.so"
loadmodule "json.so"
loadmodule "rest_client.so"

And including the rating.cfg file:

include_file "rating.cfg"

The included file contains the routes used by OpenSIPS to interact with the Rating Agent.

Authorization

Let's start with the authorization method of the Rating Agent.
In the opensips.cfg we can notice this piece of code:

if (is_method("INVITE") && !has_totag()) {
  route(rating_authorization);
}

The route rating_authorization is run for every new INVITE method that does not have a to tag.

This route prepares the json to be sent to the Rating Agent populating the following variables to the HTTP request body:

$json(query) := "{}";
$json(query/transaction_tag) = $ci;
$json(query/account_tag) =$fU;
$json(query/source) = $fu;
$json(query/destination) = $tu;

The REST client than triggers an async call to the Rating Agent URL to the endpoint /v1/authorization

async(rest_post("http://rating-agent:8000/v1/authorization", $json(query), , $var(http_rb), $var(ct), $var(rcode)), rating_authorization_response);

The route rating_authorization_response is the most complex of the Canyan Rating OpenSIPS integration routes. It handles the response from the Agent to the authorization endpoint request. Let's break it down and see what it does.

$var(rc) = $rc;
if ($var(rc) < 0) {
    xlog("async rest_put() failed with $var(rc), user=$fU\n");
    send_reply(500, "Server Internal Error 3");
    exit;
}
if ($var(rcode) >= 300) {
    if ($var(rcode) == 408) {
        xlog("async rest_put() rcode=$var(rcode), user=$fU\n");
        send_reply(500, "Server Internal Error 4");
    } else {
        send_reply(500, "Rating Server Error");
        xlog("L_ALERT", "Rating Agent is not available\n");
    }
    exit;
}
...

The first check is performed on the response from the Agent module. It checks if the HTTP response is not 200. Then if there are no errors it checks if the response contains the expected fields:

$json(response) := $var(http_rb);
if($json(response/authorized) == NULL) {
    xlog("L_ERR", "Cannot parse json data\n");
    route(rating_authorization_response_temporarily_unavailable);
}

It uses custom routes to handle the logging and SIP responses in case of errors. In this case the rating_authorization_response_temporarily_unavailable is triggered if the data sent back from the Rating Agent are not what we expect.

Next is the switch ($json(response/unauthorized_reason)) block if the authorized response is false. The different unauthorized reasons are handled (NOT_FOUND, NOT_ACTIVE, UNREACHEABLE_DESTINATION, TOO_MANY_RUNNING_TRANSACTIONS, BALANCE_INSUFFICIENT)

if ($json(response/authorized) != true) {
  if ($json(response/unauthorized_reason) != "") {
    switch ($json(response/unauthorized_reason)) {
      case "NOT_FOUND":
        route(rating_authorization_not_found);
      case "NOT_ACTIVE":
        route(rating_authorization_not_active);
        break;
      case "UNREACHEABLE_DESTINATION":
        route(rating_authorization_response_unreacheable_destination);
        break;
      case "TOO_MANY_RUNNING_TRANSACTIONS":
        route(rating_authorization_response_too_many_running_transactions);
        break;
      case "BALANCE_INSUFFICIENT":
        route(rating_authorization_response_balance_insufficient);
        break;
      default:
        route(rating_authorization_response_forbidden);
    }
  }
}

If the authorization is true we use the dialog timeout based on the response from the Rating Agent:

$var(max_available_units) = $(json(response/max_available_units){s.int});
if ($var(max_available_units) <= 0) {
  route(rating_authorization_response_balance_insufficient);
}
$DLG_timeout = $var(max_available_units);

Then we handle the prioritized carriers list sent to us from Canyan Rating. The list is calculated by the Rating Engine using the pricing and an LCR algorithm and the account preferences. The destination is then set from the first carrier in the list:

$json(carriers) := $json(response/carriers);
$var(carrier) = $json(carriers[0]);

if ($var(carrier) == "") {
  send_reply(600, "No carrier found");
  xlog("L_ERR", "ERROR_rating_authorization_response var carriers: $dlg_val(carriers) - empty\n");
  exit;
}

$var(reg) = "/UDP:/sip:/g";
$du = $(var(carrier){re.subst,$var(reg)});
route(relay);

At the end of the route the relay route is called.

As said the rating_authorization_response route is the most complex one, let's take a look now how to handle the beginning and end of a call.

Begin Transaction

The route rating_begin_transaction is called within the if statement of the event route for the E_DLG_STATE_CHANGED event. The same event route is used for the end transaction. If the $param(new_state) is set to 4 it means that the dialog has started and that's where we trigger the route rating_begin_transaction.

event_route[E_DLG_STATE_CHANGED] {
  $avp(ci) = $param(callid);
  if($param(new_state) == 4 ) {
    route(rating_begin_transaction);
  }
  ...
}

The route prepares the following variables for the Rating Agent HTTP async call:

  • transaction_tag ($avp(ci))

  • timestamp ($Ts)

Please note that the $avp(ci) is populated in the event route with the $param(callid) from the event.

There is no need for other fields because the Rating Engine uses the transaction_tag to lookup for the data sent by the rating_authorization route.

Then the route rating_begin_transaction_response that handles the response from the Agent is doing some simple checks with rcode. The code then does the check of the content of the response which consists in checking if the variable ok is true.

If some of the checks are not good the route sends a 500 reply to che client with a message that the Rating is not available. The route also prints an xlog with alert log level and the http error.

$var(rc) = $rc;
if ($var(rc) < 0) {
  xlog("rating_begin_transaction_response: async rest_put() failed with $var(rc), user=$fU\n");
  send_reply(500, "Server Internal Error 3");
  exit;
}
if ($var(rcode) >= 300) {
  if ($var(rcode) == 408) {
    xlog("rating_begin_transaction_response: async rest_put() rcode=$var(rcode), user=$fU\n");
    send_reply(500, "Server Internal Error 4");
  } else {
    xlog("L_ALERT", "Rating: Agent is not available\n");
    send_reply(500, "Rating Server Error");
  }
  exit;
}

If some of the checks are not good the route sends a 500 reply to the client with a message that the Rating is not available. The route also prints an xlog with alert log level and the HTTP error.

Then there is a last check on the content of the response body if the field ok is true:

$json(response) := $var(http_rb);
if ($var(rcode) == 200) {
  if ($json(response/ok)) {
    return;
  }
}

Now let's see the end of the call which is almost the same.

End Transaction

The route rating_end_transaction sets the following variables to be sent to the Agent via HTTP async call:

  • transaction_tag ($avp(ci))

  • timestamp ($Ts)

As for the begin transaction the end is also triggered by the event route:

event_route[E_DLG_STATE_CHANGED] {
  $avp(ci) = $param(callid);
  ...
  if($param(new_state) == 5 ) {
    route(rating_end_transaction);
  }
}

The Agent endpoint we send the REST call is /v1/end_transaction

Please note that the $avp(ci) is populated in the event route with the $param(callid) from the event. Unfortunately we now loose the dialog variables because there is no more dialog but all we need is the transaction_tag because the Rating Engine is capable of restoring the needed information gathered from previous requests.

As for the Begin Transaction there is a simple rating_end_transaction_response route that handles the response from the Agent and checks the correct HTTP response code and the value of the ok boolean variable inside the response body. It sends SIP error replies and logs with alert log level if there is any issue.

Testing

The integration repository contains also tests that can be run with a simple make test-opensips after the make docker-start command. Please refer to those tests to see how this implementation with OpenSIPS is tested with Canyan Rating.

The tests are run with the open source tool canyan-tester, our open-source testing tool. For usage information and a better understanding of the testing process with canyan-tester please refer to the tester README.

Now, let's take a look at one of the tests files, the test_unauthorized_check_transaction.yaml file where we can immediately spot the three main sections:

  • setup

  • workers

  • check

The setup section runs API calls to create the needed accounts, price lists, and rates to perform the call.

Then the sipp worker in the workers section uses the test_unauthorized_check_transaction.xml scenario file to make a call towards our just configured kamailio box.

In the end the check section of our test_unauthorized_check_transaction.yaml performs an API call and expects the transaction of the sipp call to be registered in the Canyan Rating system with all the information on why the call has not been authorized.

Conclusions

As seen it's quite easy to integrate Canyan Rating with OpenSIPS. For more details on how to run Canyan Rating, you should take a look at the Running Canyan Rating section of the documentation.

The above tests insert testing data on the fly but you should take a look at the Inserting Data Tutorial to be able to insert your own data in Canyan Rating.

References
...
Aleksandar Sošić
Co-Founder

Entrepreneur with 15+ years of experience as a software developer and IT consultant.