Hello,
(cross-posting because the details are useful for both devels and
users)
Yesterday during the IRC meeting about Kamailio, one group of
topics was about Kamailio 5.0, and the new feature of using
embedded interpreters for external programming languages was
highly debated. So I am trying to clarify some of its aspects.
The new framework is referenced as kemi, chosen by me as an
abbreviation for Kamaialio EMbedded Interface.
We already had embedded interpreters for many years, respectively:
- Lua via app_lua module (probably most developed and most used)
- Perl via app_perl module (iirc, the oldest embedded interpreter)
- Python via app_python (I discovered during the last days that it
has quite a lot of features, just not documented -- never used it
before)
- Java via app_java (this one is a bit of a blackhole for me,
never looked at it, don't know what it offers/how it is supposed to
be used)
- .NET group of languages via app_mono (e.g., C#, but also some
variants of JavaScript, Python and maybe even Java)
To clarify, an embedded interpreter means that Kamailio is linking
to itself the interpreter of that language and kamailio becomes the
interpreter of the language. It is not launching the standard
interpreter for that language, therefore it is very fast at runtime.
Moreover, kamailio is extending the language with new modules,
giving access to Kamailio C functions. In other words, for example
with Python, Kamailio becomes equivalent to the "python" application
plus two extensions (modules) named Router (developed in the past
inside app_python) and KSR (developed for kemi).
At this moment, the kemi was implemented in app_lua and app_python
modules.
To understand what is all about, I am going to give an example with
a function from maxfwd module. In default kamailio.cfg next
statement is present:
if (!mf_process_maxfwd_header("10")) {
sl_send_reply("483","Too Many Hops");
exit;
}
mf_process_maxfwd_header("10") is the function exported by maxfwd:
-
https://www.kamailio.org/docs/modules/stable/modules/maxfwd.html#maxfwd.f.mf_process_maxfwd_header
The parameter can be as a static number (like above) or a number
provided via a variable, like next:
$var(p) = 10;
mf_process_maxfwd_header("$var(p)")
The corresponding C code for this config function is:
static int w_process_maxfwd_header(struct sip_msg* msg, char* str1,
char* str2)
{
int mfval;
if (get_int_fparam(&mfval, msg, (fparam_t*) str1) < 0) {
LM_ERR("could not get param value\n");
return -1;
}
return process_maxfwd_header(msg, mfval);
}
The function get_int_fparam() is practically returning the int value
of the parmeter, no matter it was a static value or a variable, then
process_maxfwd_header(...) is executed with the int parameter. The
msg is the SIP message structure present mostly everywhere in the C
code related to the classic config interpreter.
To be used inside the config file, the C function has to be exported
via a cmd_export_t structure added to "struct module_exports
exports", respectively:
static cmd_export_t cmds[]={
...
{"mf_process_maxfwd_header",
(cmd_function)w_process_maxfwd_header, 1,
fixup_var_int_1, 0, REQUEST_ROUTE},
...
To get the same function in a kemi interpreter (Lua, Python at this
moment), following C code was added:
static sr_kemi_t sr_kemi_maxfwd_exports[] = {
{ str_init("maxfwd"), str_init("process_maxfwd"),
SR_KEMIP_INT, process_maxfwd_header,
{ SR_KEMIP_INT, SR_KEMIP_NONE, SR_KEMIP_NONE,
SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
},
{ {0, 0}, {0, 0}, 0, NULL, { 0, 0, 0, 0, 0, 0 } }
};
int mod_register(char *path, int *dlflags, void *p1, void *p2)
{
sr_kemi_modules_add(sr_kemi_maxfwd_exports);
return 0;
}
The structure sr_kemi_t sr_kemi_maxfwd_exports exports directly the
existing function process_maxfwd_header(), which is also executed by
the function from kamailio.cfg after resolving the parameter to an
int value. In the case of kemi, the Lua or Python will give directly
an integer value, so no need for resolving that with a fixup
wrapper.
The mod_register() function is something already existing, providing
a way to execute code when a module was loaded (e.g., is used for
many years by modules such as tls, mysql, etc.). In this case it was
added, because maxfwd didn't need anything at load time before.
Practically, no C code extension was written to maxfwd module from
point of view of SIP routing. Just an existing function was
exported.
Now, what's actually the role of this kemi framework ...
The app_lua already exported the kamailio.cfg function
mf_process_maxfwd_header() as sr.process_maxfwd() since Kamailio
v3.1, see its usage in the tutorial:
- https://kb.asipto.com/kamailio:usage:k32-lua-routing#lua_script
But a Lua specific wrapper had to be written:
-
https://github.com/kamailio/kamailio/blob/master/modules/app_lua/app_lua_exp.c#L1327
All the other embedded interpreters would have needed to get a
wrapper as well to export this function. That's obviously costing
time and maintenance resources.
So kemi is a common framework to export C function to any of the
embedded interpreters, meaning that once a function is exported from
a module, it becomes available to all embedded interpreters, no
longer being necessary to write a specif wrapper per interpreter.
The app_* module needs a bit of coding to use the kemi exported
functions (already done for Lua and Python), but then nothing needs
to be done for new exported functions by modules.
As a matter of fact, lots of functions can be just exported, as you
could see in the example with maxfwd. In some cases, maybe some
functions need to be split, so the fixup handling is done separately
and then calls a common C function.
So, what we had so far:
[native kamailio cfg script] <===> [fixup framework for
variables] <===> [C implementation of function]
The above stays in place, no change to it. What we got extra:
[embedded language script] <===> [kemi framework] <===>
[C implementation of function]
In addition, kemi allows to write the routing blocks all in embedded
language. Core parameters, loading modules and modules' parameters
stay like so far, but there is no need to write request_route{},
failure_route, etc.. blocks. Some functions with a specific names
have to be written in the embedded language script, see more at:
- https://www.kamailio.org/wiki/devel/config-engines
In several days I expect to be possible to have routing blocks of
kamailio-basic.cfg written via kemi interpreters. That will allow
testing the performance impact.
Right now Lua and Python use different mechanism of exporting
Kamailio C functions, the one from Python should be faster, but
relies on defining some C wrappers inside the module. Anyhow Lua
might be faster as interpreter itself -- and at the end it can just
get the same mechanism as Python.
app_lua already supports reloading the routing script without
restarting kamailio, one of the features asked from time to time.
Besides that, of course, a big benefit is the access to a very large
set of extensions already available as Lua/Python libraries. Also
important, many peoples are already familiar with these languages
and there is plenty of good documentation about those languages.
The classic/native kamailio.cfg format (and the interpreter for it)
stays there -- same common C functions are shared with kemi, so any
addition in the future will be visible to everywhere. The native
interpreter will remain the option for extreme optimizations for
those that deal with enormous amount of SIP traffic (e.g., heavy
load balancers), but I expect that at least Lua will be in pair of
performances with a registrar deployment.
I hope I could shed some light on various aspects of the new
framework for embedded interpreters. Expect very soon news about
ability to build full routing logic in Lua/Python, hopefully many of
you will join to test the results and compare with native
alternatives.
Cheers,
Daniel
--
Daniel-Constantin Mierla
http://www.asipto.com
http://twitter.com/#!/miconda - http://www.linkedin.com/in/miconda
Kamailio World Conference, Berlin, May 18-20, 2016 - http://www.kamailioworld.com