User-Defined Functions

Overview

VoIP systems are very generic. Their basic call flow is strictly defined in various RFCs, however there are still a plenty of ways to customize the business logic on behalf of the protocol itself.

Kamailio SIP proxy is one example of such system. It strictly follows RFC 3261. At the same time it gives you a card blanche for modifying each SIP message accordingly to your business logic. That is what makes every Kamailio configuration specific and harder for engineers to properly set granular VoIP monitoring.

We designed the SIP3 platform with generic VoIP systems in mind. User-Defined Functions (UDFs) let engineers get granular monitoring data, based on various user-defined and service attributes.

At the moment the SIP3 platform lets you write UDFs using Groovy or JavaScript.

This document will walk you though the process of writing UDFs. For more information about deployment, please read the SIP3 Installation Guide.

User-Defined Function Base Structure

To better understand SIP3 UDF structure let’s analyze following sip_message_udf example:

Groovy
def eventBus = vertx.eventBus()                                 (1)
eventBus.localConsumer("sip_message_udf", { event ->            (2)
    // `event.body` is a Map<String, Object>:
    // {
    //  "src_addr" : String,
    //  "src_host" : String (Optional),
    //  "src_port" : Integer,
    //  "dst_addr" : String,
    //  "dst_host" : String (Optional),
    //  "dst_port" : Integer,
    //  "payload" : Map<String, String>,
    //  "attributes" : Map<String, String|Boolean>
    // }
    def packet = event.body()

    def sip_message = packet['payload']                         (3)
    if (sip_message['from'].matches('<sip:100@.*')) {
        packet['attributes']['robocall'] = true                 (4)
    }

    event.reply(true)                                           (5)
})
JS
var eventBus = vertx.eventBus();                                (1)
eventBus.localConsumer("sip_message_udf", function (event) {    (2)
    // `event.body` is a Map<String, Object>:
    // {
    //  "src_addr" : String,
    //  "src_host" : String (Optional),
    //  "src_port" : Integer,
    //  "dst_addr" : String,
    //  "dst_host" : String (Optional),
    //  "dst_port" : Integer,
    //  "payload" : Map<String, String>,
    //  "attributes" : Map<String, String|Boolean>
    // }
    var packet = event.body();

    var sip_message = packet['payload'];                        (3)
    if (sip_message['from'].match('<sip:100@.*')) {
        packet['attributes']['robocall'] = true;                (4)
    }

    event.reply(true);                                          (5)
});
1 Under the hood SIP3 uses Vert.x framework which makes it very simple to write code extensions in different languages. You can find more about that in the Vert.x Documentation. Alternatively you can copy the code snippet above and modify it according to your needs.
2 "sip_message_udf" is a name of the SIP3 UDF endpoint. As an input it receives a SIP3 packet with generic payload represented as a Map<String, Object>.
3 "sip_message_udf" endpoint receives as a payload a SIP message represented as a Map<String, String> where keys are SIP header names and values are corresponding SIP header values.
4 In our example robocall is an User-Defined Attribute which is assigned to a SIP3 packet. There is no limit of the amount of user defined attributes that can be assigned to a packet. Every assigned attribute is automatically populated as a search criteria in the Advanced Search and all related Service Metrics.
5 Each UDF has to return boolean result which may be used by SIP3 Salto to define further message flow.

User-Defined Attributes

As mentioned in the previous section, the main purpose of any SIP3 UDF is assigning String or Boolean attribute to a SIP3 packet according to its business logic.

We call such attributes User-Defined Attributes (UDAs). UDAs can bring your SIP3 experience to a next level if you follow the following rule:

Always choose String attributes with a limited amount of possible values. Otherwise, it may affect your database performance and can even bring it down. For instance, if you have a mobile application: operation_system or application_version are good choice of UDAs.

Service Attributes

Most attributes assigned within a UDF are UDAs. Along with those each UDF endpoint carries out its own set of attributes called Service Attributes. Service Attributes help resolving various call aggregation and correlation problems.

SIP3 correlates same call legs by strictly matching From and To users. Now, let’s imagine that you have a SIP node which receives all SIP messages containing From headers with US numbers in international format (From: <sip:13027030026@sip3.io>). The SIP node handles SIP messages and routes them further while modifying the From header according to the US national number format (From: <sip:3027030026@sip3.io>). Such configuration breaks the SIP3 call correlation. Fortunately, there is a solution to that problem. We can simply do a pattern matching and assign the results to the caller and callee Service Attributes defined for the "sip_message_udf" endpoint, as shown below.

Groovy
def eventBus = vertx.eventBus()
eventBus.localConsumer("sip_message_udf", { event ->
    def packet = event.body()

    def from_header = packet['payload']['from']
    def matcher = (from_header =~ /1(\d*)/)
    if (matcher) {
        packet['attributes']['caller'] = matcher[0][1]
    }

    event.reply(true)
})
JS
var eventBus = vertx.eventBus();
eventBus.localConsumer("sip_message_udf", function (event) {
    var packet = event.body();

    var from_header = packet['payload']['from'];
    var matcher = from_header.match(/1(\d*)/);
    if (matcher != null) {
        packet['attributes']['caller'] = matcher[1];
    }

    event.reply(true);
});

The example above shows just a simple customization which can be done by analyzing SIP message content. However, you can build a very complex business logic using vertx object. Read in the Vert.x Documentation and this tutorial how to send HTTP requests or query a remote database just in a few lines of code.

Endpoints

Below you can find a list of all avaialble service UDF endpoints.

"packet_udf"

1. Description

"packet_udf" UDF is called for every packet received by SIP3 Salto. The main purpose of this UDF is to filter duplicates (e.g. traffic sent from SIP3 Captain and Heplify can be filtered only by this function).

2. Payload

"packet_udf" UDF receives as a payload a Map<String, Object> of src_addr, src_port, src_host, dst_addr, dst_port and dst_host.

3. User-Defined and Service Attributes

"packet_udf" UDF doesn’t support User-Defined and Service Attributes.

4. Usage Example

Following example shows how to filter traffic passing between SBC and SSW hosts:

Groovy
def eventBus = vertx.eventBus()
eventBus.localConsumer("packet_udf", { event ->
    // `event.body` is a Map<String, Object>:
    // {
    //  "sender_addr" : String,
    //  "sender_host" : String (Optional),
    //  "sender_port" : Integer,
    //  "payload" : Map<String, Object>,
    //      "src_addr" : String,
    //      "src_host" : String (Optional),
    //      "src_port" : Integer,
    //      "dst_addr" : String,
    //      "dst_host" : String (Optional),
    //      "dst_port" : Integer,
    // }
    def packet = event.body()

    if (packet['sender_host'] == 'SBC'
            && (packet['payload']['src_host'] == 'SSW' || packet['payload']['dst_host'] == 'SSW')) {
        event.reply(false)
    } else {
        event.reply(true)
    }
})
JS
var eventBus = vertx.eventBus();
eventBus.localConsumer("packet_udf", function (event) {
    // `event.body` is a Map<String, Object>:
    // {
    //  "sender_addr" : String,
    //  "sender_host" : String (Optional),
    //  "sender_port" : Integer,
    //  "payload" : Map<String, Object>,
    //      "src_addr" : String,
    //      "src_host" : String (Optional),
    //      "src_port" : Integer,
    //      "dst_addr" : String,
    //      "dst_host" : String (Optional),
    //      "dst_port" : Integer,
    // }
    var packet = event.body();

    if (packet['sender_host'] == 'SBC'
        && (packet['payload']['src_host'] == 'SSW' || packet['payload']['dst_host'] == 'SSW')) {
        event.reply(false);
    } else {
        event.reply(true);
    }
});

"sip3_message_udf"

1. Description

"sip3_message_udf" UDF is called for every SIP message receieved by SIP3 Salto. UDAs and Service Attributes assigned within the UDF are used for futher registration and call aggregation.

2. Payload

"sip3_message_udf" UDF receives as a payload a Map<String, String> where keys are SIP header names and values are corresponding SIP header values.

3. User-Defined and Service Attributes

"sip3_message_udf" UDF has no restrictions on assigning User-Defined Attributes. However it considers caller, callee and x_call_id as a Service Attributes used to resolve various call correlation problems.

4. Usage Example

Following example shows how to define and assign robocall attribute:

Groovy
def eventBus = vertx.eventBus()
eventBus.localConsumer("sip_message_udf", { event ->
    // `event.body` is a Map<String, Object>:
    // {
    //  "src_addr" : String,
    //  "src_host" : String (Optional),
    //  "src_port" : Integer,
    //  "dst_addr" : String,
    //  "dst_host" : String (Optional),
    //  "dst_port" : Integer,
    //  "payload" : Map<String, String>,
    //  "attributes" : Map<String, String|Boolean>
    // }
    def packet = event.body()

    def sip_message = packet['payload']
    if (sip_message['from'].matches('<sip:100@.*')) {
        packet['attributes']['robocall'] = true
    }

    event.reply(true)
})
JS
var eventBus = vertx.eventBus();
eventBus.localConsumer("sip_message_udf", function (event) {
    // `event.body` is a Map<String, Object>:
    // {
    //  "src_addr" : String,
    //  "src_host" : String (Optional),
    //  "src_port" : Integer,
    //  "dst_addr" : String,
    //  "dst_host" : String (Optional),
    //  "dst_port" : Integer,
    //  "payload" : Map<String, String>,
    //  "attributes" : Map<String, String|Boolean>
    // }
    var packet = event.body();

    var sip_message = packet['payload'];
    if (sip_message['from'].match('<sip:100@.*')) {
        packet['attributes']['robocall'] = true;
    }

    event.reply(true);
});