At Zaleos we have a strong background on Real Time protocols such as SIP or WebRTC. One of the Open Source products that we use most is called Kamailio, which is an Open Source SIP Server that is able to handle thousands of VoIP calls per second. In this post, we're going to share a clean and efficient setup that we use at Zaleos to write C++ Kamailio modules. This way we can benefit from the use of a pure object-oriented language to implement the business logic.

Kamailio has a very efficient C core and it is easily extensible using its modules. Anyone can write a Kamailio module, plug it and use it from the main Config Script (.cfg) logic.

Kamailio system architecture

Existing modules for Kamailio vary from relational and not relational databases connectors and JSON manipulation functions to Dial-plan and Routing procedures. Kamailio is written in C language and most modules are usually written in C. We're going to do a C++ approach this time, let's see if you like it!


Creating a C++ setup

The idea is to have a self-contained C++ setup which will be the starting point of our development. There we can implement our C++ classes and tests. Isolating it from Kamailio at this stage is key, we need to achieve this and boost the developer's efficiency (and sanity!). The needed outputs from this setup are the following:

  • The output of our implementation must be either a Static C/C++ Library (.a) or Shared C/C++ Library (.so) -or both- that we can link against later on.
  • Somehow we need some sort of command to run Unit Tests written in our implementation. A binary that links Unit Tests against our produced library and we could run it to validate the processes would be great.
  • We also need to have some Example compiled programs to run our implementation. A set of binaries (each one for each example) that link against our produced library and we could run them in a stand-alone mode would be awesome. (We need to easily use Valgrind in our C/C++ implementations!)

The type of tool that we choose to generate these outputs will drive how the setup looks like. Initially, we thought to choose Make as the most convenient option (Kamailio uses it on its compilation process), but we wanted to pursue the idea of several builds from the same source tree and a cross-platform library so we decided to use CMake. By the way, at Zaleos, we're researching Open Source Bazel to be used as the main builder tool for all our products in the future.

In this example, the library will be named libzzz and we'll have a dedicated folder including:

Subfolder Content
src/test Source files - Embedded testing framework
src/examples Source files - Examples that use the library
src/zzz Source files - Library implementation with tests
lib Output folder where the library will be created after build
cmake Random CMake external functions
build Starting folder to trigger the build process
bin Output folder where the binaries (examples and tests) will be created after build

The Core libzzz class

This example library implements a Core class that needs a number (integer) and text (std::string) and exposes methods to access to both the number, the text and a method which returns the number incremented by one. Easy enough to start. More information in the implementation file and in the header file. The main program (C++) which acts as an example can be found here.

Creating a C interface

We're also providing a C class to use the library from C based programs. This is a clean approach to use C++ code from C implementations and will be really useful later on when we plug the library into Kamailio. More information in the implementation file and in the header file. The main program (C) which acts as an example can be found here.

Testing framework

We need a solid testing setup. After studying different options, we opted for Catch as the testing framework. It's a cool and easy-to-use Unit Testing library which also provides Test Driven Development and/or Behavior Driven Development approaches. In our example, we've implemented basic tests for the Core class, more information here.

Compiling the library

Some basic commands to compile and generate the desired outputs:

Compiling the library using CMake and Make

Executing Examples and Test binaries

We can easily run the C++ and C examples and the Test binaries generated and linked against the libzzz. Notice all the binaries generated can be also run using Valgrind, making sure we're not leaking memory and everything is strong from the ground-up.

Executing examples and test binaries

Integrating this into a Continuous Integration pipeline to validate that the library is in a good shape for each commit should be pretty straight-forward. Ideally, our CI setup must run all the tests using Valgrind making sure the exit code of the test is good. Notice one feature that we're missing on this setup is the Code Coverage metric for the library, which obviously would be a pretty good measure to have.


Creating Kamailio basic module

Now that we have our library nicely setup and we can link against it, we can start thinking on how to glue this into Kamailio. We'll create a new module (called zzz) which implements an interface to our library so we can instantiate a new Core object when Kamailio starts and provide the functions of the C interface of the library to the main Config Script (.cfg). Good start would be to use the method that returns the number incremented by one of our in-memory Core object when a new SIP call is made.

Kamailio running zzz module system architecture

Kamailio has some online documentation that can be useful to implement your module from scratch, but our experience tells us that a very useful source of ideas comes from inspecting other's modules source code. We're going to use the latest version Kamailio major branch, Kamailio v5.

The implementation of the zzz module can be found here. This module exports two params called number and text, it instantiates a Core object when Kamailio starts and destroys it when Kamailio finishes its execution. This module also exports a function called get_incremented_number_mod which returns the number of our Core object (incremented by one). Another interesting thing to mention is the Makefile, which dynamically compiles our libzzz library and links it to the module.

As part of Kamailio compilation process, you have to make sure you include the module into the Kamailio's folder (where modules live) and include it as part of the compilation process. You have information about compiling Kamailio from sources here. You have to see an output similar to:

Creating libzzz library...
pushd ./libzzz/build; cmake ../; make; popd
...
[100%] Built target eg_hello_world_c
make[4]: Leaving directory `/tmp/kkkamailio.0.0.1.Pe90/kkkamailio/rpmbuild/BUILD/kkkamailio-0.0.1/src/modules/zzz/libzzz/build'
make[3]: Leaving directory 
...
-----------------------------------------------
g++ -shared  -m64 -Wl,-O2 -Wl,-E   zzz_mod.o  -L/include/lib -L/usr/local/lib/ -L/usr/local/lib -L./libzzz/lib -lzzz  -L../../lib/srutils/ -lsrutils -Wl,-rpath,/usr/local/lib64/kkkamailio/ -o zzz.so
...

In the main Kamailio main Config script, we need to initialize the module (load and provide values for the params - 555 as the number). We're also using the get_incremented_number_mod function to use it as the SIP Code response by default to any incoming INVITE.

# -------------------------------------------------------------------------------
# zzz module
# -------------------------------------------------------------------------------
loadmodule "zzz.so"
modparam("zzz", "number", 555)
modparam("zzz", "text", "This is a not-used text from cfg")
# -------------------------------------------------------------------------------
...
request_route {
   ...
   $var(incremented_number) = "";
   get_incremented_number_mod("$var(incremented_number)");
   sl_send_reply("$var(incremented_number)", "Zaleos Tutorial");
   exit;
}
...
# -------------------------------------------------------------------------------

As you can see in the following screenshot, sending a call to the Kamailio server running this zzz module returns a SIP message with SIP Code 556 - which is 555 incremented using our libzzz library ;-)

Sngrep SIP capture of the call made to Kamailio running zzz module

Conclusions

We've demonstrated how we can create a basic C++ setup to implement your business logic and bridge it to a Kamailio C based module later on. This is a good starting point if you need to use Kamailio and do your coding using an object-oriented programming language such as C++.


References

(0) Zaleos Github repo of this post.
(1) A great C++ library setup using both CMake & Bazel building practices: served.
(2) CMake vs Make.
(3) Mixing C and C++ code.
(4) Using C++ Functions and Objects from C.
(5) Asciinema and asciicast2gif used for the screencasts.
(6) Sngrep. Ncurses SIP Messages flow viewer.