Integrating Canyan Rating with Kamailio

by Aleksandar Sošić April 27, 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 kamailio

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

Kamailio

Kamailio is an Open Source SIP Server released under GPL, able to handle thousands of call setups per second. Kamailio can be used to build large platforms for VoIP and realtime communications – presence, WebRTC, Instant messaging, and other applications. Moreover, it can be easily used for scaling up SIP-to-PSTN gateways, PBX systems, or media servers like Asterisk™, FreeSWITCH™, or SEMS.

Prerequisites

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

Also, there is a make test-kamailio 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 Kamailio instance: Kamailio 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 Kamailio, we will use the HTTP_ASYNC_CLIENT Module to avoid binding Kamailio workers with synchronous HTTP calls.

Kamailio Configuration

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

Please note that the Kamailio 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 "http_async_client.so"
loadmodule "jansson.so"
loadmodule "dialog.so"

And including the rating.cfg file:

include_file "rating.cfg"

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

Authorization

Let's start with the authorization method of the Rating Agent.
In the kamailio.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:

jansson_set("string", "transaction_tag", $ci, "$var(query)");
jansson_set("string", "account_tag", $fU, "$var(query)");
jansson_set("string", "source", $fu, "$var(query)");
jansson_set("string", "destination", $tu, "$var(query)");
$http_req(body) = $var(query);

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

http_async_query(RATING_AGENT_URL + "/v1/end_transaction", "RATING_AUTHORIZATION_RESPONSE");

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

if(!$http_ok || $http_rs >= 300) {
    if ($http_rs == 408) {
        send_reply(408, "Rating request timeout");
        xlog("L_ALERT", "Rating request timeout\n");
    }
    send_reply(500, "Rating is not available");
    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 200 then it checks if the response contains the expected fields:

if(!jansson_get("authorized", $http_rb, "$var(authorized)")) {
    xlog("L_ERR", "Rating: 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 ($var(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 ($var(authorized) != 1) {
  jansson_get("unauthorized_reason", $http_rb, "$var(unauthorized_reason)");
  if ($var(unauthorized_reason) != "") {
    switch ($var(unauthorized_reason)) {
      case "NOT_FOUND":
        route(RATING_AUTHORIZATION_RESPONSE_NOT_FOUND);
      case "NOT_ACTIVE":
        route(RATING_AUTHORIZATION_RESPONSE_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);
    }
  }
  exit;
}

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

jansson_get("max_available_units", $http_rb, "$var(max_available_units)");
$var(max_available_units) = $(var(max_available_units){s.int});
dlg_set_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 and the second one is set in a dialog variable du_secondary used in the failure_route in case the first destination is not responding.

jansson_get("carriers", $http_rb, "$dlg_var(carriers)");
jansson_set("array", "carriers", $dlg_var(carriers), "$var(carriers)");
jansson_array_size("carriers", $var(carriers), "$var(carriers_size)");

# there is no carriers in the response
if ($var(carriers_size) <= 0) {
  xlog("L_ERR", "ERROR: RATING_AUTHORIZATION_RESPONSE var carriers: $var(carriers) - empty\n");
  send_reply(600, "No carrier found");
  exit;
}

jansson_get("carriers[0]", $var(carriers), "$dlg_var(du_primary)");
$dlg_var(du_primary) = $(dlg_var(du_primary){s.replace,UDP,sip});

# we've got more than one carrier, we can store the failover in a dlg_var
if ($var(carriers_size) >= 2) {
  jansson_get("carriers[1]", $var(carriers), "$dlg_var(du_secondary)");
  $dlg_var(du_secondary) = $(dlg_var(du_secondary){s.replace,UDP,sip});
}
$du = $dlg_var(du_primary);

route(SIPOUT);

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

Here's the code that manages the failure:

# next destination - only for 500 or local timeout
if (t_check_status("500") or (t_branch_timeout() and !t_branch_replied())) {
  if (defined $dlg_var(du_secondary) && !strempty($dlg_var(du_secondary))) {
    # relay the packet to the secondary carrier
    $du = $dlg_var(du_secondary);
    $dlg_var(du_secondary) = "";
    route(RELAY);
    exit;
  }
}

As said this 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 with the event route dialog:start.

event_route[dialog:start] {
  route(RATING_BEGIN_TRANSACTION);
}

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

  • transaction_tag ($ci)

  • account_tag ($fU)

  • source ($fu)

  • destination ($tu)

Then the route RATING_BEGIN_TRANSACTION_RESPONSE that handles the response from the Agent is doing some simple checks like http_ok and if the HTTP response code is 200. After that the check of the content of the response is performed and it consists of checking if the variable ok is true.

if ($http_ok && $http_rs == 200) {
    jansson_get("ok", $http_rb, "$var(ok)");
    if ($var(ok) == true) {
      return;
    }
  }
  send_reply(500, "Rating not available");
  xlog("L_ALERT", "Rating: Not available: $http_err\n");
  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.

End Transaction

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

  • transaction_tag ($ci)

  • account_tag ($fU)

  • source ($fu)

  • destination ($tu)

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

event_route[dialog:end] {
  route(RATING_END_TRANSACTION);
}

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

There is a simple RATING_END_TRANSACTION_RESPONSE route that handles the response and checks the correct HTTP response and the value of the ok boolean variable inside the response body. It sends SIP error replies and logs with alert log level the issue.

Testing

The integration repository contains also tests that can be run with a simple make test-kamailio after the make docker-start command. Please refer to those tests to see how this implementation with Kamailio 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 Kamailio. 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.