Arithmetic and other custom functions in Kamailio
Although Kamailio supports basic arithmetic, it does so only using configuration variables and not dynamic variables. Due to its flexibility, it is just a matter of time until you will come up with an idea where you would need to use dynamic variables for some form of arithmetic calculations.
In version 5.7.0 released on May 17th 2023, a Math module was released with 5 functions, mainly around logarithmic and square root computation. At the time of writing, no basic stuff like addition, subtraction, multiplication and division are included. So what can be done? One solution that might come to mind is to use a shell script but that might not be the quickest or the safest as demonstrated by the Enable Security team in this post. Another solution is to use one of the numerous supported scripting languages. In this post, I will be using my favorite, Lua.
Brief intro to using scripting languages
With the release of Kamailio version 5.0.0, a framework called Kamailio Embedded Interpreter Interface aka KEMI was added. Its purpose was to make the writing of the SIP routing logic possible in other scripting languages like Lua, Python, JavaScript etc. The idea behind how it works is simple, for each scripting language, a module to link Kamailio to the scripting language interpreter is created. At the time of writing, the languages supported by the KEMI framework are JavaScript, Lua , Python , Python3 , Ruby and Squirrel. More information on the framework can be found here
One benefit of this framework is that you can for example use the Kamailio native scripting languages and call a script from one of the KEMI supported languages, create a variable in the script and have the variable available in the native scripting language after the script runs 🤯. Yes, read that again and think of the endless possibilities!
So you would need to have the language installed, have a script with the function we want to run, ensure the module for the scripting language is initialized at startup and you are set. Let’s work on our script now.
The script
Let’s write an example Lua script with four functions: add, subtract, multiply and divide.
-- Script with functions to add, subtract, multiply or divide 2 variables
function add(a, b)
local num1 = tonumber(a) or 1
local num2 = tonumber(b) or 1
local result = num1+num2
KSR.pv.sets("$var(lua_add)", result); -- Put the result in a variable we will use later
end
function subtract(a, b)
local num1 = tonumber(a) or 1
local num2 = tonumber(b) or 1
local result = num1-num2
KSR.pv.sets("$var(lua_subtract)", result); -- Put the result in a variable we will use later
end
function multiply(a, b)
local num1 = tonumber(a) or 1
local num2 = tonumber(b) or 1
local result = num1*num2
KSR.pv.sets("$var(lua_multiply)", result); -- Put the result in a variable we will use later
end
function divide(a, b)
local num1 = tonumber(a) or 1
local num2 = tonumber(b) or 1
local result = num1/num2
KSR.pv.sets("$var(lua_divide)", result); -- Put the result in a variable we will use later
end
Now we have the script, let’s go to work on the Kamailio configuration file.
First we include the module:
loadmodule "app_lua.so"
Then we call the script we want and have it initialized at startup. This makes it cached so that when the script is called it is not always read from disk.
# ----- Lua parama -----
modparam("app_lua", "load", "/etc/kamailio/script.lua")
The obvious next question is … “Does that mean that if I change the script, I need to restart Kamailio so it caches the file?” The answer is NO!. Kamailio has you covered with a module option to just reload the file without restarting Kamailio. Although enabled by default, let’s make sure it stays that way by adding the module option.
# ----- Lua parama -----
modparam("app_lua", "reload", 1)
The setup is done, now let’s run the script in our routing logic. You are free to let your imagination run wild. My example is just to show how fun Kamailio can be. We will take the ‘From’ and ‘To’ user values from an INVITE, work our math magic on them, insert the results as headers and forward the request to port 5060 of a fictive endpoint 1.2.3.4 😎 Let’s have some fun.
...
if (is_method("INVITE")) {
lua_run("add", "$fU", "$tU");
append_hf("X-add: $var(lua_add)\r\n");
lua_run("subtract", "$fU", "$tU");
append_hf("X-subtract: $var(lua_subtract)\r\n");
lua_run("multiply", "$fU", "$tU");
append_hf("X-multiply: $var(lua_multiply)\r\n");
lua_run("divide", "$fU", "$tU");
append_hf("X-divide: $var(lua_divide)\r\n");
$du = "sip:1.2.3.4:5060";
forward();
exit;
}
...
Using sipexer, an awesome SIP command line tool, we will call extension 10 from extension 58 and see what the headers look like ….
Ohh the value of the header ‘X-divide’ is the current Kamailio version, what a coincidence! 😄
Speed
“Hmmm… still not sure. I think it might be slow” … OK, let’s add timers and see.
In the config file we will add the Benchmark Module which helps log function profiling information. Time is measured in microseconds.
loadmodule "benchmark.so"
# ----- benchmark params -----
modparam("benchmark", "enable", 1)
modparam("benchmark", "loglevel", 1)
In the config let’s just look at the time needed to execute one of the functions, say the addition
...
if (is_method("INVITE")) {
bm_start_timer("cfg_add"); # Start the timer
lua_run("add", "$fU", "$tU");
bm_log_timer("cfg_add"); # Stop the timer
xlog("L_INFO","Lua add execution took: $BM_time_diff us\n"); # Log the time difference
append_hf("X-add: $var(lua_add)\r\n");
...
We send another INVITE using sipexer and check the logs …
Mar 26 17:43:12 kama-test /usr/sbin/kamailio[3837]: NOTICE: {1 477368 INVITE a643e2a3-37dc-47a6-a9d5-9f9c8a7f4e54} benchmark [benchmark.c:340]: _bm_log_timer(): benchmark (timer cfg_add [1]): 48 [ msgs/total/min/max/avg - LR: 1/48/48/48/48.000000 | GB: 3/223/48/127/74.333333]
Mar 26 17:43:12 kama-test /usr/sbin/kamailio[3837]: INFO: {1 477368 INVITE a643e2a3-37dc-47a6-a9d5-9f9c8a7f4e54} <script>: Lua add execution took: 48 us
It took 45 micro seconds in an LXC container on my old Dell micro with an Intel i5-7500T CPU. I think it is fast enough for me.
Updating the script
As mentioned before, the default module setting allows for updating/re-caching the script without restarting Kamailio. Since it is possible to load multiple scripts at startup, the reload command takes in an id as an option to know which script needs reloading. So, the first step is to find our script id.
Kamailio installation comes with the kamcmd
tool that helps interect with Kamailio. To list the loadeded scripts and their respective ids, use the command
kamcmd app_lua.list
In this case we get
0: [/etc/kamailio/script.lua]
The script id is 0. To reload our script, we use another kamcmd
command in the format kamcmd app_lua.reload [script_id]
so in our case
kamcmd app_lua.reload 0
Final thoughts
There you have it, arithmetic calculations in Kamailio. Of course, you can run whatever function you want and most importantly remember that you can use any of the supported embedded scripting languages. Also, do not be afraid of the performance hits because as has been demonstrated in this post and by Daniel-Constantin Mierla in this exhaustive article where Python was compared to Kamailio’s native scripting language, the difference is barely noticeable.