How To Script Advanced FreeSWITCH Integrations with OpenSIPS 2.4

verastream-integrating-puzzle-pieces

Happy New Year 2018! In this follow-up article, we’re going to take a look at a major feature that Santa merged into the OpenSIPS development branch, just about a couple of days before Christmas: a series of advanced FreeSWITCH integration capabilities.

It is now possible to fully control FreeSWITCH within the OpenSIPS script. This is a lot more useful than what we originally started with – CPU-fair load balancing. If you haven’t read the initial OpenSIPS-FreeSWITCH integration article, you can find it here.

Top-Level View of the Integration

Features

We wanted to go beyond a simple event HEARTBEAT connection manager that helps improve the load balancing quality, and actually offer the OpenSIPS script writer the power to work with bi-directional, generic communication primitives between OpenSIPS and the FreeSWITCH ESL! Here is what you can do with OpenSIPS 2.4:

  • subscribe to generic FreeSWITCH events via DB, MI or modparam
  • catch and manipulate FreeSWITCH event information within an event_route
  • run a FreeSWITCH ESL command on any FreeSWITCH node, from any route.
  • provision FreeSWITCH ESL endpoints via a new SQL table (freeswitch)
  • reload the pool of FreeSWITCH servers via MI, during runtime.

Modules

You will notice that OpenSIPS 2.4 actually has two FreeSWITCH-related modules: “freeswitch” and “freeswitch_scripting”. Strictly talking from an ESL interaction point of view, here is how various OpenSIPS areas talk to the ESL:

architecture-freeswitch-integration
Both OpenSIPS modules and the config file can talk to FreeSWITCH

Notice the important role of the “freeswitch” module. It acts as a liaison between FreeSWITCH boxes and the various OpenSIPS modules that want to talk to them. It transparently manages ESL connections, and reuses them in case multiple modules request from or push data to the same ESL endpoint. It will even reconnect dropped connections or close those which are no longer needed (e.g. this would happen if we were to delete a FreeSWITCH node from the freeswitch table and hit reload).

Next, notice the new kid on the block: “freeswitch_scripting”! This module exposes all FreeSWITCH events and interaction primitives to the OpenSIPS script. It also includes DB support and a series of useful MI commands – these allow you to provide a list of ESL events to subscribe for on each node, as well as the ability to resize and manage your FreeSWITCH server pool without restarting OpenSIPS.

More detailed information regarding the exact parameters and functions that were introduced can be found in the updated module documentation pages of freeswitch and freeswitch_scripting.

Example Integration: DTMF-based Call Transfer

Below is an example of an integration scenario that becomes possible starting with OpenSIPS 2.4. We will break the script down into sections, so we can discuss each one in-depth. Finally, the entire script will also be available for download.

Scenario

We want to add multi-language support lines for the customers of our business. When the customer dials one of the support numbers, they will hit an IVR asking for the desired language. Following from that, they will be forwarded to the support line of their choice—the implementation of such a FreeSWITCH IVR is out of the scope of this example, we will assume it’s there and ready.

Implementation

We start with the basic opensips.cfg file. The first step is to be able to match the support numbers. We can use the drouting module along with MySQL DB support to quickly achieve performant, in-memory prefix lookups:

loadmodule "db_mysql.so"
loadmodule "drouting.so"
modparam("drouting", "db_url", "mysql://root:liviusmysqlpassword@localhost/opensips")

# $param(1) - 1 if the R-URI IP:port part should be updated
route [goes_to_support] {
    if ($param(1) == 1)
        $var(flags) = "";
    else
        $var(flags) = "C";

    if (do_routing("0", "$var(flags)"))
        return(1);

    return(-1);
}

, with the drouting DB provisioning looking like:

MariaDB [opensips]> select * from dr_gateways;
+----+------+------+------------+-------+------------+-------+------------+-------+--------+-------------+
| id | gwid | type | address    | strip | pri_prefix | attrs | probe_mode | state | socket | description |
+----+------+------+------------+-------+------------+-------+------------+-------+--------+-------------+
|  1 | pbx1 |    1 | 10.0.0.246 |     0 |       NULL |  NULL |          0 |     0 |   NULL |        NULL |
+----+------+------+------------+-------+------------+-------+------------+-------+--------+-------------+
1 row in set (0.00 sec)

MariaDB [opensips]> select * from dr_rules;
+--------+---------+--------+---------+----------+---------+--------+-------+-------------+
| ruleid | groupid | prefix | timerec | priority | routeid | gwlist | attrs | description |
+--------+---------+--------+---------+----------+---------+--------+-------+-------------+
|      1 | 0       | 1000   |    NULL |        0 |    NULL |   pbx1 |  NULL | NULL        |
+--------+---------+--------+---------+----------+---------+--------+-------+-------------+
1 row in set (0.00 sec)

Next, we need a way to hook into FreeSWITCH. Conveniently for us, FreeSWITCH has a useful DTMF event that gets fired when the user hits a phone pad digit. We can subscribe OpenSIPS to this event and instruct FreeSWITCH to transfer the user to the actual support line based on his input digit.

We first load the modules we plan on using. Notice the json module which will prove very handy in order to parse the JSON-encoded events pushed by FreeSWITCH:

loadmodule "cachedb_local.so"

loadmodule "freeswitch.so"
loadmodule "freeswitch_scripting.so"
modparam("freeswitch_scripting", "fs_subscribe", "fs://:ClueCon@10.0.0.246:8021/database?DTMF,CHANNEL_STATE,CHANNEL_ANSWER,HEARTBEAT")

loadmodule "event_route.so"
loadmodule "json.so"

We then catch the E_FREESWITCH event into an event_route, and parse its entire JSON body. After making sure it’s a “DTMF” event of a support line IVR dial, we pass it on for further processing.

event_route [E_FREESWITCH] {
    fetch_event_params("$var(event_name);$var(fs_box);$var(event_body)");
    xlog("FreeSWITCH event $var(event_name) from $var(fs_box), with $var(event_body)\n");

    $json(body) := $var(event_body);

    if ($var(event_name) == "DTMF") {
        $rU = $json(body/Caller-Destination-Number);
        if (!$rU) {
            xlog("SCRIPT:DTMF:ERR: missing body/Caller-Destination-Number field!\n");
            return;
        }

        if (route(goes_to_support, 0))
            route(FREESWITCH_XFER_BY_DTMF_LANG);
    }
}

An added problem that we need to handle are the extra DTMF digits (besides the first one) which we will need to ignore. For this, we will use the local cache, and populate some temporary keys (10 min lifetime) with the call’s “Unique-ID”, as given by FreeSWITCH. You can argue that there is still a race condition on the cache_fetch() call (see below), and you are correct. For this, you may use the cfgutils module and the locking primitives defined there, but this is too advanced for this demo script.

Finally, we transfer the call based on the user chosen language using the freeswitch_esl() function:

route [FREESWITCH_XFER_BY_DTMF_LANG] {
    # this call has already been transferred
    if (cache_fetch("local", "DTMF-$json(body/Unique-ID)", $var(_)))
        return;

    switch ($json(body/DTMF-Digit)) {
    case "1":
        xlog("transferring to English support line\n");
        freeswitch_esl("bgapi uuid_transfer $json(body/Unique-ID) -aleg 1001",
                       "$var(fs_box)", "$var(output)");
        break;
    case "2":
        xlog("transferring to Spanish support line\n");
        freeswitch_esl("bgapi uuid_transfer $json(body/Unique-ID) -aleg 1002",
                       "$var(fs_box)", "$var(output)");
        break;
    default:
        xlog("DEFAULT: transferring to English support line\n");
        freeswitch_esl("bgapi uuid_transfer $json(body/Unique-ID) -aleg 1001",
                       "$var(fs_box)", "$var(output)");
    }

    xlog("ran FS uuid_transfer, output: $var(output)\n");

    cache_store("local", "DTMF-$json(body/Unique-ID)", "OK", 600);
}

And we’re good to go. The full script can be downloaded from here.

Future Improvements

The latest additions have really opened Pandora’s box with regards to how OpenSIPS and FreeSWITCH may interact with one another. While we tried to cover as much ground as we could, there is always a scenario that is still not possible or a feature that is missing.

So be sure to check out these changes (you can even work with the master branch, it’s quite stable) and let us know your suggestions and feedback–there is plenty of time to get them implemented until the stable 2.4 release which we plan on unveiling during the Amsterdam OpenSIPS Summit 2018 in May!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s