Python Module for SER, OpenSIPS and Kamailio

What this project is about?

Sip Express Router (aka SER) and its spin-offs OpenSIPS and Kamailio provide fast, reliable and extensible engine for building VoIP networks and products. However, scripting capabilities of those proxies are quite limited, often requiring coding modules in plain C to implement complex logic.

This project is aimed at closing this gap by allowing using persistent Python object instances to implement routing logic. Persistency is very important, since it means that there is virtually no run-time overhead when calling Python code in the course of request/reply processing. All parsing and initialization happens when proxy starts (of course unless one uses "reflection" features of the Python language).

The Python interface is thread-friendly and GIL-compatible, so that it's possible to use built-in threading features of the Python language to execute asynchronous Python code that runs in "parallel" with the proxy itself. This code could for example do background data updating and caching, monitor for and respond to externals events possibly altering routing logic based on changing conditions and so on.

Some of the common uses for this feature:

  • Access to persistent data storage (database) for which no native module exists, but Python module is available;
  • Fetching and caching data in background for performance and other purposes;
  • Implementing complex, "environment-aware" routing policies;
  • Using third-party SIP Python code to extend SIP functionality of the proxy.

Python module structure

Python module to be used to extend proxy should provide the following facilities:

  • Module initialization function (mod_init() by default). This function takes no arguments and has to be defined at the global level of the Python module. It should return instance of the handler object. The function is called ONCE, BEFORE the proxy forks and changes performed by this functions to the Python module state will be SHARED by all proxy instances;
  • Handler object, instantiated and returned by the mod_init() function should provide child_init() method that takes child Id as the only parameter, just like any native C module does. The method performs per-child initialization and expected to return 0 on success and -1 on failure. The method is called in each proxy child separately AFTER a proxy forks, so all changes performed by that method will remain PRIVATE to each proxy instance;
  • Handler object may also provide one or more methods to be called from the routing script using python_exec() function. Each method can take 1 or 2 arguments, first arguments being the SIP message context object (see below) and the other arguments is optional string parameter of the python_exec() function. Value returned by the method is available in the routing script.

Hello World example

from Router import LM_ERR
from time import time

class Router(object):
    def child_init(self, child_id):
        LM_ERR('Router::child_init(%d)\n' % child_id)
        return 0

    def hello_world(self, msg, param):
        LM_ERR('Router::hello_world(%s), type = %s\n' % (param, msg.Type))
        msg.call_function('append_hf', 'X-Hello: World, now is %f\r\n' % time())
        return 0

def mod_init():
    inst = Router()
    return inst

Corresponding routing script for this example:

...
loadmodule "/usr/local/lib/ser/modules/python.so"
loadmodule "/usr/local/lib/ser/modules/textops.so" # <- we need this for append_hf function
...
modparam("python", "script_name", "/home/foobar/router.py")
...
route {
...
    # Call method hello_world() passing "some_value" as an optional argument
    python_exec("hello_world", "some_value");
...
}

SIP message object

SIP message object that is passed to the method called from the routing script provides access to the proxy internal message representation, functions and operations. It should be noted, however, that this object is valid in the contect of that function only and should not be saved in the Python environment state for later use. After the function returns, the object is invalidated and any attempt to access it will cause exception or even crash.

The following attributes are available:

  • Type: message type. "SIP_REQUEST" for requests and "SIP_REPLY" for replies;
  • RURI: request RURI. Only available for requests;
  • Method: SIP method name. Only available for requests;
  • Status: SIP status code string. Only available for replies;
  • src_address: (IP, port) tuple representing source address of the message.

The following methods are available:

  • rewrite_ruri(uri): Rewrite Request-URI with the provided argument;
  • set_dst_uri(uri): Set destination URI;
  • getHeader(name): Get SIP header field value by name;
  • call_function(arg1, arg2): Invoke function exported by the other module (arg1 and arg2 are optional).

(to be continued)